@speedofme/mcp 1.0.10 → 1.0.11

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.js CHANGED
@@ -1,4 +1,4 @@
1
- var u=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var U=u((jt,ye)=>{ye.exports={name:"@speedofme/mcp",version:"1.0.10",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=u((Nt,H)=>{var k=require("fs"),ge=require("path"),we=require("os"),L=ge.join(we.homedir(),".speedofme");function xe(){k.existsSync(L)||k.mkdirSync(L,{recursive:!0})}var w="https://cdn3.speedof.me",Te=`${w}/sf/`,Pe=`${w}/ul`,Ae=`${w}/location/`,Ee=`${w}/location/pops.json`,F="https://api.speedof.me",_e=`${F}/service/auth`,De=`${F}/service/result`,C="https://speedof.me/";async function Le(t,e={}){let s={Referer:C,...e.headers};return fetch(t,{...e,headers:s})}var j=.1;function Me(t,e){return e<1&&(e=1),N(t/e/125e3*(1+j))}function N(t){return Math.round(t*100)/100}function $e(){return Date.now()}function Ie(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function Oe(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function ve(){if(!T)try{T=U().version||"1.0.0"}catch{T="1.0.0"}return T}var Re=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],be=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];H.exports={SPEEDOFME_DIR:L,ensureSpeedofmeDir:xe,CDN_BASE:w,SAMPLE_FILES_PATH:Te,UPLOAD_URL:Pe,LOCATION_URL:Ae,POPS_URL:Ee,API_AUTH_URL:_e,API_RESULT_URL:De,REFERER:C,cdnFetch:Le,NET_LOSS:j,getSpeed:Me,round:N,getCurrTime:$e,calcJitter:Ie,generateUuid:Oe,getVersion:ve,SAMPLES:Re,GB_SAMPLES:be}});var B=u((Ht,q)=>{var{API_AUTH_URL:Ue}=d();async function ke(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(Ue,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let n={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(n[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}q.exports={authenticate:ke}});var V=u((qt,J)=>{var{getCurrTime:z,calcJitter:Fe,cdnFetch:Ce,SAMPLE_FILES_PATH:je}=d(),G=10;async function Ne(t={}){let{onProgress:e}=t,s=[];for(let c=0;c<G;c++){let r=z();try{await Ce(`${je}?r=${Math.random()}`,{method:"HEAD"});let a=z()-r;if(s.push(a>0?a:1),e&&e({type:"latency",pass:c+1,percentDone:Math.round((c+1)/G*100),currentLatency:s[c]}),a>999)break;await new Promise(o=>setTimeout(o,1))}catch{s.push(1e3)}}let n=Math.min(...s),i=Math.floor(Fe(s));return{latency:n,jitter:i}}J.exports={runLatencyTest:Ne}});var Z=u((Bt,X)=>{var{getCurrTime:Y,getSpeed:He,cdnFetch:qe,SAMPLES:K,GB_SAMPLES:Be,SAMPLE_FILES_PATH:ze}=d();async function Ge(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:n=0,onProgress:i}=t,c=s?[...K,...Be]:[...K],r=n>0?Math.min(n,c.length):c.length,a=0,o=0,l=0,p=c[0][0],m=null;for(;a<r;){let[S,h]=c[a];a++,p=S;let A=`${ze}${h}.bin?r=${Math.random()}`;try{let E=Y(),_=await qe(A);if(!_.ok)throw new Error(`HTTP ${_.status}`);let R=await _.arrayBuffer(),D=R.byteLength;m=Buffer.from(R);let b=(Y()-E)/1e3;if(o=He(D,b),o>l&&(l=o),i&&i({type:"download",pass:a,percentDone:100,bytesReceived:D,totalBytes:D,currentSpeed:o,maxSpeed:l}),b>=e)break}catch(E){if(a===1)throw new Error(`Download test failed: ${E.message}`);break}}return{download:o,maxDownload:l,passes:a,lastSampleSize:p,lastSampleData:m}}X.exports={runDownloadTest:Ge}});var oe=u((zt,ne)=>{var P=require("fs"),ee=require("path"),{getCurrTime:W,getSpeed:Je,SPEEDOFME_DIR:te,ensureSpeedofmeDir:Ve,UPLOAD_URL:Ye,REFERER:Ke}=d(),Q=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],Xe=[134217728,268435456],se="upload-sample.bin",Ze=67108864,We=268435456;async function Qe(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:n,supportGbTest:i=!1,onProgress:c}=t,r,a,o;if(s&&n){let l=Math.min(s,536870912),p=l/2;r=n,a=[];for(let m=1;m<=10;m++){let S=Math.pow(2,5-m),h=Math.min(Math.floor(l/S),p);if((a.length===0||h>a[a.length-1])&&a.push(h),h>=p)break}o=p}else{let l=i?We:Ze;a=i?[...Q,...Xe]:Q,await tt(l),r=P.readFileSync(ee.join(te,se)),o=null}return et({dataBuffer:r,sampleSizes:a,maxSizeCheck:o,sustainTime:e,onProgress:c})}async function et(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:n,sustainTime:i,onProgress:c}=t,r=0,a=0,o=0;for(let l of s){r++;try{let p=e.slice(0,l),m=W(),S=await fetch(Ye,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:Ke},body:p});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(W()-m)/1e3;a=Je(l,h),a>o&&(o=a),c&&c({type:"upload",pass:r,percentDone:100,bytesSent:l,totalBytes:l,currentSpeed:a,maxSpeed:o});let A=n&&l>=n;if(h>=i||A)break}catch(p){if(r===1)throw new Error(`Upload test failed: ${p.message}`);break}}return{upload:a,maxUpload:o,passes:r}}async function tt(t){let e=ee.join(te,se);if(Ve(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let n=0;n<t;n+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,n)}P.writeFileSync(e,s)}ne.exports={runUploadTest:Qe}});var ce=u((Gt,ie)=>{var{cdnFetch:ae,LOCATION_URL:st,POPS_URL:nt}=d(),g=null,x=null,re={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function ot(){return g||x||(x=(async()=>{try{let t=await ae(nt);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||re,g}catch{return g=re,g}finally{x=null}})(),x)}async function rt(){try{let t=await ot(),e=await ae(`${st}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let n=s.toUpperCase();return t[n]||n}catch{return"Unknown"}}function at(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ie.exports={getTestServer:rt,getPopName:at}});var I=u((Jt,le)=>{var y=require("fs"),it=require("path"),{SPEEDOFME_DIR:ct,ensureSpeedofmeDir:lt}=d(),f=it.join(ct,"history.json"),M=1024*1024,pt=400;function $(){try{if(y.existsSync(f)){let t=y.readFileSync(f,"utf8");return JSON.parse(t)}}catch{}return[]}function ut(t){lt();let e=Math.floor(M/pt);t.length>e&&(t=t.slice(-e)),y.writeFileSync(f,JSON.stringify(t,null,2),"utf8")}function dt(t={}){let{limit:e=10,since:s}=t,n=$();if(s){let i=new Date(s);n=n.filter(c=>new Date(c.timestamp)>=i)}return n.slice(-e).reverse()}function ft(t){try{let e=$();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),ut(e),!0}catch{return!1}}function mt(){try{return y.existsSync(f)&&y.unlinkSync(f),!0}catch{return!1}}function ht(){try{let t=$(),e=y.existsSync(f)?y.statSync(f):{size:0};return{path:f,size:e.size,count:t.length,maxSize:M,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:f,size:0,count:0,maxSize:M,error:t.message}}}le.exports={getHistory:dt,saveResult:ft,clearHistory:mt,getHistoryStats:ht,HISTORY_FILE:f}});var fe=u((Vt,de)=>{var O=require("os"),{getVersion:St}=d();function pe(){let t=O.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function ue(){return O.release()}function yt(t="sdk"){let e=St(),s=pe(),n=ue(),i=O.arch(),c=process.version;return`SpeedOfMe-${t}/${e} (${s} ${n}; ${i}) Node/${c}`}de.exports={buildUserAgent:yt,getOSName:pe,getOSVersion:ue}});var he=u((Yt,me)=>{var{authenticate:gt}=B(),{runLatencyTest:wt}=V(),{runDownloadTest:xt}=Z(),{runUploadTest:Tt}=oe(),{getTestServer:Pt}=ce(),{saveResult:At}=I(),{generateUuid:Et,API_RESULT_URL:_t}=d(),{buildUserAgent:Dt}=fe(),v=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await gt(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let n={testId:Et(),timestamp:new Date().toISOString(),source:"sdk"},i,c;try{if((s.includes("download")||s.includes("upload"))&&(n.testServer=await Pt(),this._reportProgress({type:"info",message:`Test server: ${n.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let r=await wt({onProgress:a=>this._reportProgress(a)});n.latency=r.latency,n.jitter=r.jitter,this._reportProgress({type:"info",message:`Latency: ${n.latency}ms, Jitter: ${n.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let r=await xt({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:a=>this._reportProgress(a)});n.download=r.download,n.maxDownload=r.maxDownload,i=r.lastSampleSize,c=r.lastSampleData,this._reportProgress({type:"info",message:`Download: ${n.download} Mbps (max: ${n.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let r=await Tt({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:c,supportGbTest:this.supportGbTest,onProgress:a=>this._reportProgress(a)});n.upload=r.upload,n.maxUpload=r.maxUpload,this._reportProgress({type:"info",message:`Upload: ${n.upload} Mbps (max: ${n.maxUpload} Mbps)`})}return this.saveHistory&&At(n),await this._sendResult(n),n}catch(r){throw this.onError&&this.onError(r),r}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Dt(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(_t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};me.exports=v});var Lt=he(),{getHistory:Mt,clearHistory:$t,getHistoryStats:Kt}=I(),{getVersion:It}=d(),Se=It();function Ot(){console.log(`
1
+ var u=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var U=u((jt,ye)=>{ye.exports={name:"@speedofme/mcp",mcpName:"me.speedof/speed-test",version:"1.0.11",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=u((Nt,H)=>{var k=require("fs"),ge=require("path"),we=require("os"),L=ge.join(we.homedir(),".speedofme");function xe(){k.existsSync(L)||k.mkdirSync(L,{recursive:!0})}var w="https://cdn3.speedof.me",Te=`${w}/sf/`,Pe=`${w}/ul`,Ae=`${w}/location/`,Ee=`${w}/location/pops.json`,F="https://api.speedof.me",_e=`${F}/service/auth`,De=`${F}/service/result`,C="https://speedof.me/";async function Le(t,e={}){let s={Referer:C,...e.headers};return fetch(t,{...e,headers:s})}var j=.1;function Me(t,e){return e<1&&(e=1),N(t/e/125e3*(1+j))}function N(t){return Math.round(t*100)/100}function $e(){return Date.now()}function Ie(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function Oe(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function ve(){if(!T)try{T=U().version||"1.0.0"}catch{T="1.0.0"}return T}var Re=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],be=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];H.exports={SPEEDOFME_DIR:L,ensureSpeedofmeDir:xe,CDN_BASE:w,SAMPLE_FILES_PATH:Te,UPLOAD_URL:Pe,LOCATION_URL:Ae,POPS_URL:Ee,API_AUTH_URL:_e,API_RESULT_URL:De,REFERER:C,cdnFetch:Le,NET_LOSS:j,getSpeed:Me,round:N,getCurrTime:$e,calcJitter:Ie,generateUuid:Oe,getVersion:ve,SAMPLES:Re,GB_SAMPLES:be}});var B=u((Ht,q)=>{var{API_AUTH_URL:Ue}=d();async function ke(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(Ue,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let o={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(o[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}q.exports={authenticate:ke}});var V=u((qt,J)=>{var{getCurrTime:z,calcJitter:Fe,cdnFetch:Ce,SAMPLE_FILES_PATH:je}=d(),G=10;async function Ne(t={}){let{onProgress:e}=t,s=[];for(let c=0;c<G;c++){let r=z();try{await Ce(`${je}?r=${Math.random()}`,{method:"HEAD"});let a=z()-r;if(s.push(a>0?a:1),e&&e({type:"latency",pass:c+1,percentDone:Math.round((c+1)/G*100),currentLatency:s[c]}),a>999)break;await new Promise(n=>setTimeout(n,1))}catch{s.push(1e3)}}let o=Math.min(...s),i=Math.floor(Fe(s));return{latency:o,jitter:i}}J.exports={runLatencyTest:Ne}});var Z=u((Bt,X)=>{var{getCurrTime:Y,getSpeed:He,cdnFetch:qe,SAMPLES:K,GB_SAMPLES:Be,SAMPLE_FILES_PATH:ze}=d();async function Ge(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:o=0,onProgress:i}=t,c=s?[...K,...Be]:[...K],r=o>0?Math.min(o,c.length):c.length,a=0,n=0,l=0,p=c[0][0],m=null;for(;a<r;){let[S,h]=c[a];a++,p=S;let A=`${ze}${h}.bin?r=${Math.random()}`;try{let E=Y(),_=await qe(A);if(!_.ok)throw new Error(`HTTP ${_.status}`);let R=await _.arrayBuffer(),D=R.byteLength;m=Buffer.from(R);let b=(Y()-E)/1e3;if(n=He(D,b),n>l&&(l=n),i&&i({type:"download",pass:a,percentDone:100,bytesReceived:D,totalBytes:D,currentSpeed:n,maxSpeed:l}),b>=e)break}catch(E){if(a===1)throw new Error(`Download test failed: ${E.message}`);break}}return{download:n,maxDownload:l,passes:a,lastSampleSize:p,lastSampleData:m}}X.exports={runDownloadTest:Ge}});var ne=u((zt,oe)=>{var P=require("fs"),ee=require("path"),{getCurrTime:W,getSpeed:Je,SPEEDOFME_DIR:te,ensureSpeedofmeDir:Ve,UPLOAD_URL:Ye,REFERER:Ke}=d(),Q=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],Xe=[134217728,268435456],se="upload-sample.bin",Ze=67108864,We=268435456;async function Qe(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:o,supportGbTest:i=!1,onProgress:c}=t,r,a,n;if(s&&o){let l=Math.min(s,536870912),p=l/2;r=o,a=[];for(let m=1;m<=10;m++){let S=Math.pow(2,5-m),h=Math.min(Math.floor(l/S),p);if((a.length===0||h>a[a.length-1])&&a.push(h),h>=p)break}n=p}else{let l=i?We:Ze;a=i?[...Q,...Xe]:Q,await tt(l),r=P.readFileSync(ee.join(te,se)),n=null}return et({dataBuffer:r,sampleSizes:a,maxSizeCheck:n,sustainTime:e,onProgress:c})}async function et(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:o,sustainTime:i,onProgress:c}=t,r=0,a=0,n=0;for(let l of s){r++;try{let p=e.slice(0,l),m=W(),S=await fetch(Ye,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:Ke},body:p});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(W()-m)/1e3;a=Je(l,h),a>n&&(n=a),c&&c({type:"upload",pass:r,percentDone:100,bytesSent:l,totalBytes:l,currentSpeed:a,maxSpeed:n});let A=o&&l>=o;if(h>=i||A)break}catch(p){if(r===1)throw new Error(`Upload test failed: ${p.message}`);break}}return{upload:a,maxUpload:n,passes:r}}async function tt(t){let e=ee.join(te,se);if(Ve(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let o=0;o<t;o+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,o)}P.writeFileSync(e,s)}oe.exports={runUploadTest:Qe}});var ce=u((Gt,ie)=>{var{cdnFetch:ae,LOCATION_URL:st,POPS_URL:ot}=d(),g=null,x=null,re={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function nt(){return g||x||(x=(async()=>{try{let t=await ae(ot);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||re,g}catch{return g=re,g}finally{x=null}})(),x)}async function rt(){try{let t=await nt(),e=await ae(`${st}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let o=s.toUpperCase();return t[o]||o}catch{return"Unknown"}}function at(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ie.exports={getTestServer:rt,getPopName:at}});var I=u((Jt,le)=>{var y=require("fs"),it=require("path"),{SPEEDOFME_DIR:ct,ensureSpeedofmeDir:lt}=d(),f=it.join(ct,"history.json"),M=1024*1024,pt=400;function $(){try{if(y.existsSync(f)){let t=y.readFileSync(f,"utf8");return JSON.parse(t)}}catch{}return[]}function ut(t){lt();let e=Math.floor(M/pt);t.length>e&&(t=t.slice(-e)),y.writeFileSync(f,JSON.stringify(t,null,2),"utf8")}function dt(t={}){let{limit:e=10,since:s}=t,o=$();if(s){let i=new Date(s);o=o.filter(c=>new Date(c.timestamp)>=i)}return o.slice(-e).reverse()}function ft(t){try{let e=$();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),ut(e),!0}catch{return!1}}function mt(){try{return y.existsSync(f)&&y.unlinkSync(f),!0}catch{return!1}}function ht(){try{let t=$(),e=y.existsSync(f)?y.statSync(f):{size:0};return{path:f,size:e.size,count:t.length,maxSize:M,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:f,size:0,count:0,maxSize:M,error:t.message}}}le.exports={getHistory:dt,saveResult:ft,clearHistory:mt,getHistoryStats:ht,HISTORY_FILE:f}});var fe=u((Vt,de)=>{var O=require("os"),{getVersion:St}=d();function pe(){let t=O.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function ue(){return O.release()}function yt(t="sdk"){let e=St(),s=pe(),o=ue(),i=O.arch(),c=process.version;return`SpeedOfMe-${t}/${e} (${s} ${o}; ${i}) Node/${c}`}de.exports={buildUserAgent:yt,getOSName:pe,getOSVersion:ue}});var he=u((Yt,me)=>{var{authenticate:gt}=B(),{runLatencyTest:wt}=V(),{runDownloadTest:xt}=Z(),{runUploadTest:Tt}=ne(),{getTestServer:Pt}=ce(),{saveResult:At}=I(),{generateUuid:Et,API_RESULT_URL:_t}=d(),{buildUserAgent:Dt}=fe(),v=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await gt(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let o={testId:Et(),timestamp:new Date().toISOString(),source:"sdk"},i,c;try{if((s.includes("download")||s.includes("upload"))&&(o.testServer=await Pt(),this._reportProgress({type:"info",message:`Test server: ${o.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let r=await wt({onProgress:a=>this._reportProgress(a)});o.latency=r.latency,o.jitter=r.jitter,this._reportProgress({type:"info",message:`Latency: ${o.latency}ms, Jitter: ${o.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let r=await xt({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:a=>this._reportProgress(a)});o.download=r.download,o.maxDownload=r.maxDownload,i=r.lastSampleSize,c=r.lastSampleData,this._reportProgress({type:"info",message:`Download: ${o.download} Mbps (max: ${o.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let r=await Tt({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:c,supportGbTest:this.supportGbTest,onProgress:a=>this._reportProgress(a)});o.upload=r.upload,o.maxUpload=r.maxUpload,this._reportProgress({type:"info",message:`Upload: ${o.upload} Mbps (max: ${o.maxUpload} Mbps)`})}return this.saveHistory&&At(o),await this._sendResult(o),o}catch(r){throw this.onError&&this.onError(r),r}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Dt(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(_t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};me.exports=v});var Lt=he(),{getHistory:Mt,clearHistory:$t,getHistoryStats:Kt}=I(),{getVersion:It}=d(),Se=It();function Ot(){console.log(`
2
2
  SpeedOf.Me CLI v${Se}
3
3
 
4
4
  Usage:
@@ -25,7 +25,7 @@ Examples:
25
25
  speedofme --secret SOM_SECRET_xxx --sustain 4
26
26
  speedofme history --limit 5
27
27
  speedofme history --clear
28
- `)}function vt(t){let e={command:"test",secret:process.env.SOM_API_SECRET,progress:!1,json:!1,tests:null,sustain:6,limit:10,clear:!1,help:!1,version:!1},s=0;for(;s<t.length;){let n=t[s];n==="history"?e.command="history":n==="--secret"&&t[s+1]?e.secret=t[++s]:n==="-p"||n==="--progress"?e.progress=!0:n==="-j"||n==="--json"?e.json=!0:n==="--tests"&&t[s+1]?e.tests=t[++s].split(",").map(i=>i.trim()):n==="--sustain"&&t[s+1]?e.sustain=parseInt(t[++s],10):n==="--limit"&&t[s+1]?e.limit=parseInt(t[++s],10):n==="--clear"?e.clear=!0:n==="-h"||n==="--help"?e.help=!0:(n==="-v"||n==="--version")&&(e.version=!0),s++}return e}function Rt(t){let e=["","=".repeat(50),"Speed Test Results","=".repeat(50),`Test ID: ${t.testId}`,`Timestamp: ${t.timestamp}`,`Server: ${t.testServer||"Unknown"}`];return t.latency!==void 0&&(e.push(`Latency: ${t.latency} ms`),e.push(`Jitter: ${t.jitter} ms`)),t.download!==void 0&&e.push(`Download: ${t.download.toFixed(2)} Mbps`),t.upload!==void 0&&e.push(`Upload: ${t.upload.toFixed(2)} Mbps`),e.push("=".repeat(50)),e.join(`
29
- `)}function bt(t){if(t.length===0)return"No test history found.";let e=["",`Found ${t.length} result(s):`,""];return t.forEach((s,n)=>{let i=new Date(s.timestamp).toLocaleString(),c=`${n+1}. ${i}`,r=[];s.download&&r.push(`DL: ${s.download.toFixed(1)} Mbps`),s.upload&&r.push(`UL: ${s.upload.toFixed(1)} Mbps`),s.latency&&r.push(`Lat: ${s.latency} ms`),r.length>0&&(c+=` - ${r.join(", ")}`),e.push(c)}),e.join(`
30
- `)}async function Ut(t){let{secret:e,progress:s,json:n,tests:i,sustain:c}=t;e||(console.error("Error: API secret required."),console.error("Set SOM_API_SECRET env var or use --secret flag."),console.error("Get your secret at: https://speedof.me/api/portal/"),process.exit(1));let r=["download","upload","latency"];if(i){let o=i.filter(l=>!r.includes(l));o.length>0&&(console.error(`Error: Invalid test type: ${o.join(", ")}`),console.error(`Valid options: ${r.join(", ")}`),process.exit(1))}let a=new Lt({apiSecret:e,sustainTime:c,clientType:"cli"});s&&(a.onProgress=o=>{let l=new Date().toISOString().split("T")[1].replace("Z","");o.type==="info"?console.log(`[${l}] ${o.message}`):o.type==="latency"?console.log(`[${l}] Latency pass ${o.pass}/10: ${o.currentLatency?.toFixed(0)} ms`):o.type==="download"?console.log(`[${l}] Download pass ${o.pass}: ${o.currentSpeed?.toFixed(2)} Mbps`):o.type==="upload"&&console.log(`[${l}] Upload pass ${o.pass}: ${o.currentSpeed?.toFixed(2)} Mbps`)}),a.onError=o=>{console.error(`Error: ${o.message}`)},n||console.log("Starting speed test...");try{let o={};i&&(o.tests=i);let l=await a.run(o);console.log(n?JSON.stringify(l,null,2):Rt(l))}catch(o){n?console.log(JSON.stringify({error:o.message},null,2)):console.error(`
31
- Test failed: ${o.message}`),process.exit(1)}}async function kt(t){let{limit:e,clear:s}=t;if(s){$t()?console.log("History cleared."):(console.error("Failed to clear history."),process.exit(1));return}let n=Mt({limit:e});console.log(bt(n))}async function Ft(){let t=process.argv.slice(2),e=vt(t);if(e.version){console.log(`speedofme v${Se}`);return}if(e.help){Ot();return}e.command==="history"?await kt(e):await Ut(e)}Ft().catch(t=>{console.error(`Fatal error: ${t.message}`),process.exit(1)});
28
+ `)}function vt(t){let e={command:"test",secret:process.env.SOM_API_SECRET,progress:!1,json:!1,tests:null,sustain:6,limit:10,clear:!1,help:!1,version:!1},s=0;for(;s<t.length;){let o=t[s];o==="history"?e.command="history":o==="--secret"&&t[s+1]?e.secret=t[++s]:o==="-p"||o==="--progress"?e.progress=!0:o==="-j"||o==="--json"?e.json=!0:o==="--tests"&&t[s+1]?e.tests=t[++s].split(",").map(i=>i.trim()):o==="--sustain"&&t[s+1]?e.sustain=parseInt(t[++s],10):o==="--limit"&&t[s+1]?e.limit=parseInt(t[++s],10):o==="--clear"?e.clear=!0:o==="-h"||o==="--help"?e.help=!0:(o==="-v"||o==="--version")&&(e.version=!0),s++}return e}function Rt(t){let e=["","=".repeat(50),"Speed Test Results","=".repeat(50),`Test ID: ${t.testId}`,`Timestamp: ${t.timestamp}`,`Server: ${t.testServer||"Unknown"}`];return t.latency!==void 0&&(e.push(`Latency: ${t.latency} ms`),e.push(`Jitter: ${t.jitter} ms`)),t.download!==void 0&&e.push(`Download: ${t.download.toFixed(2)} Mbps`),t.upload!==void 0&&e.push(`Upload: ${t.upload.toFixed(2)} Mbps`),e.push("=".repeat(50)),e.join(`
29
+ `)}function bt(t){if(t.length===0)return"No test history found.";let e=["",`Found ${t.length} result(s):`,""];return t.forEach((s,o)=>{let i=new Date(s.timestamp).toLocaleString(),c=`${o+1}. ${i}`,r=[];s.download&&r.push(`DL: ${s.download.toFixed(1)} Mbps`),s.upload&&r.push(`UL: ${s.upload.toFixed(1)} Mbps`),s.latency&&r.push(`Lat: ${s.latency} ms`),r.length>0&&(c+=` - ${r.join(", ")}`),e.push(c)}),e.join(`
30
+ `)}async function Ut(t){let{secret:e,progress:s,json:o,tests:i,sustain:c}=t;e||(console.error("Error: API secret required."),console.error("Set SOM_API_SECRET env var or use --secret flag."),console.error("Get your secret at: https://speedof.me/api/portal/"),process.exit(1));let r=["download","upload","latency"];if(i){let n=i.filter(l=>!r.includes(l));n.length>0&&(console.error(`Error: Invalid test type: ${n.join(", ")}`),console.error(`Valid options: ${r.join(", ")}`),process.exit(1))}let a=new Lt({apiSecret:e,sustainTime:c,clientType:"cli"});s&&(a.onProgress=n=>{let l=new Date().toISOString().split("T")[1].replace("Z","");n.type==="info"?console.log(`[${l}] ${n.message}`):n.type==="latency"?console.log(`[${l}] Latency pass ${n.pass}/10: ${n.currentLatency?.toFixed(0)} ms`):n.type==="download"?console.log(`[${l}] Download pass ${n.pass}: ${n.currentSpeed?.toFixed(2)} Mbps`):n.type==="upload"&&console.log(`[${l}] Upload pass ${n.pass}: ${n.currentSpeed?.toFixed(2)} Mbps`)}),a.onError=n=>{console.error(`Error: ${n.message}`)},o||console.log("Starting speed test...");try{let n={};i&&(n.tests=i);let l=await a.run(n);console.log(o?JSON.stringify(l,null,2):Rt(l))}catch(n){o?console.log(JSON.stringify({error:n.message},null,2)):console.error(`
31
+ Test failed: ${n.message}`),process.exit(1)}}async function kt(t){let{limit:e,clear:s}=t;if(s){$t()?console.log("History cleared."):(console.error("Failed to clear history."),process.exit(1));return}let o=Mt({limit:e});console.log(bt(o))}async function Ft(){let t=process.argv.slice(2),e=vt(t);if(e.version){console.log(`speedofme v${Se}`);return}if(e.help){Ot();return}e.command==="history"?await kt(e):await Ut(e)}Ft().catch(t=>{console.error(`Fatal error: ${t.message}`),process.exit(1)});
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- var p=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var q=p((Kt,Pe)=>{Pe.exports={name:"@speedofme/mcp",version:"1.0.10",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=p((Vt,B)=>{var C=require("fs"),Ae=require("path"),_e=require("os"),R=Ae.join(_e.homedir(),".speedofme");function De(){C.existsSync(R)||C.mkdirSync(R,{recursive:!0})}var w="https://cdn3.speedof.me",Ee=`${w}/sf/`,Me=`${w}/ul`,Re=`${w}/location/`,Le=`${w}/location/pops.json`,F="https://api.speedof.me",be=`${F}/service/auth`,Ue=`${F}/service/result`,H="https://speedof.me/";async function Ie(t,e={}){let s={Referer:H,...e.headers};return fetch(t,{...e,headers:s})}var j=.1;function Oe(t,e){return e<1&&(e=1),N(t/e/125e3*(1+j))}function N(t){return Math.round(t*100)/100}function ve(){return Date.now()}function ke(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function $e(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function qe(){if(!T)try{T=q().version||"1.0.0"}catch{T="1.0.0"}return T}var Ce=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],Fe=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];B.exports={SPEEDOFME_DIR:R,ensureSpeedofmeDir:De,CDN_BASE:w,SAMPLE_FILES_PATH:Ee,UPLOAD_URL:Me,LOCATION_URL:Re,POPS_URL:Le,API_AUTH_URL:be,API_RESULT_URL:Ue,REFERER:H,cdnFetch:Ie,NET_LOSS:j,getSpeed:Oe,round:N,getCurrTime:ve,calcJitter:ke,generateUuid:$e,getVersion:qe,SAMPLES:Ce,GB_SAMPLES:Fe}});var L=p((Xt,z)=>{var{API_AUTH_URL:He}=d();async function je(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(He,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let n={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(n[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}z.exports={authenticate:je}});var K=p((Wt,Y)=>{var{getCurrTime:G,calcJitter:Ne,cdnFetch:Be,SAMPLE_FILES_PATH:ze}=d(),J=10;async function Ge(t={}){let{onProgress:e}=t,s=[];for(let a=0;a<J;a++){let o=G();try{await Be(`${ze}?r=${Math.random()}`,{method:"HEAD"});let r=G()-o;if(s.push(r>0?r:1),e&&e({type:"latency",pass:a+1,percentDone:Math.round((a+1)/J*100),currentLatency:s[a]}),r>999)break;await new Promise(c=>setTimeout(c,1))}catch{s.push(1e3)}}let n=Math.min(...s),i=Math.floor(Ne(s));return{latency:n,jitter:i}}Y.exports={runLatencyTest:Ge}});var Z=p((Zt,W)=>{var{getCurrTime:V,getSpeed:Je,cdnFetch:Ye,SAMPLES:X,GB_SAMPLES:Ke,SAMPLE_FILES_PATH:Ve}=d();async function Xe(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:n=0,onProgress:i}=t,a=s?[...X,...Ke]:[...X],o=n>0?Math.min(n,a.length):a.length,r=0,c=0,u=0,l=a[0][0],f=null;for(;r<o;){let[S,h]=a[r];r++,l=S;let _=`${Ve}${h}.bin?r=${Math.random()}`;try{let D=V(),E=await Ye(_);if(!E.ok)throw new Error(`HTTP ${E.status}`);let k=await E.arrayBuffer(),M=k.byteLength;f=Buffer.from(k);let $=(V()-D)/1e3;if(c=Je(M,$),c>u&&(u=c),i&&i({type:"download",pass:r,percentDone:100,bytesReceived:M,totalBytes:M,currentSpeed:c,maxSpeed:u}),$>=e)break}catch(D){if(r===1)throw new Error(`Download test failed: ${D.message}`);break}}return{download:c,maxDownload:u,passes:r,lastSampleSize:l,lastSampleData:f}}W.exports={runDownloadTest:Xe}});var oe=p((Qt,re)=>{var P=require("fs"),te=require("path"),{getCurrTime:Q,getSpeed:We,SPEEDOFME_DIR:se,ensureSpeedofmeDir:Ze,UPLOAD_URL:Qe,REFERER:et}=d(),ee=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],tt=[134217728,268435456],ne="upload-sample.bin",st=67108864,nt=268435456;async function rt(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:n,supportGbTest:i=!1,onProgress:a}=t,o,r,c;if(s&&n){let u=Math.min(s,536870912),l=u/2;o=n,r=[];for(let f=1;f<=10;f++){let S=Math.pow(2,5-f),h=Math.min(Math.floor(u/S),l);if((r.length===0||h>r[r.length-1])&&r.push(h),h>=l)break}c=l}else{let u=i?nt:st;r=i?[...ee,...tt]:ee,await at(u),o=P.readFileSync(te.join(se,ne)),c=null}return ot({dataBuffer:o,sampleSizes:r,maxSizeCheck:c,sustainTime:e,onProgress:a})}async function ot(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:n,sustainTime:i,onProgress:a}=t,o=0,r=0,c=0;for(let u of s){o++;try{let l=e.slice(0,u),f=Q(),S=await fetch(Qe,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:et},body:l});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(Q()-f)/1e3;r=We(u,h),r>c&&(c=r),a&&a({type:"upload",pass:o,percentDone:100,bytesSent:u,totalBytes:u,currentSpeed:r,maxSpeed:c});let _=n&&u>=n;if(h>=i||_)break}catch(l){if(o===1)throw new Error(`Upload test failed: ${l.message}`);break}}return{upload:r,maxUpload:c,passes:o}}async function at(t){let e=te.join(se,ne);if(Ze(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let n=0;n<t;n+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,n)}P.writeFileSync(e,s)}re.exports={runUploadTest:rt}});var ue=p((es,ce)=>{var{cdnFetch:ie,LOCATION_URL:it,POPS_URL:ct}=d(),g=null,x=null,ae={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function ut(){return g||x||(x=(async()=>{try{let t=await ie(ct);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||ae,g}catch{return g=ae,g}finally{x=null}})(),x)}async function pt(){try{let t=await ut(),e=await ie(`${it}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let n=s.toUpperCase();return t[n]||n}catch{return"Unknown"}}function lt(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ce.exports={getTestServer:pt,getPopName:lt}});var A=p((ts,pe)=>{var y=require("fs"),dt=require("path"),{SPEEDOFME_DIR:mt,ensureSpeedofmeDir:ft}=d(),m=dt.join(mt,"history.json"),b=1024*1024,ht=400;function U(){try{if(y.existsSync(m)){let t=y.readFileSync(m,"utf8");return JSON.parse(t)}}catch{}return[]}function St(t){ft();let e=Math.floor(b/ht);t.length>e&&(t=t.slice(-e)),y.writeFileSync(m,JSON.stringify(t,null,2),"utf8")}function yt(t={}){let{limit:e=10,since:s}=t,n=U();if(s){let i=new Date(s);n=n.filter(a=>new Date(a.timestamp)>=i)}return n.slice(-e).reverse()}function gt(t){try{let e=U();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),St(e),!0}catch{return!1}}function wt(){try{return y.existsSync(m)&&y.unlinkSync(m),!0}catch{return!1}}function xt(){try{let t=U(),e=y.existsSync(m)?y.statSync(m):{size:0};return{path:m,size:e.size,count:t.length,maxSize:b,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:m,size:0,count:0,maxSize:b,error:t.message}}}pe.exports={getHistory:yt,saveResult:gt,clearHistory:wt,getHistoryStats:xt,HISTORY_FILE:m}});var fe=p((ss,me)=>{var I=require("os"),{getVersion:Tt}=d();function le(){let t=I.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function de(){return I.release()}function Pt(t="sdk"){let e=Tt(),s=le(),n=de(),i=I.arch(),a=process.version;return`SpeedOfMe-${t}/${e} (${s} ${n}; ${i}) Node/${a}`}me.exports={buildUserAgent:Pt,getOSName:le,getOSVersion:de}});var v=p((ns,he)=>{var{authenticate:At}=L(),{runLatencyTest:_t}=K(),{runDownloadTest:Dt}=Z(),{runUploadTest:Et}=oe(),{getTestServer:Mt}=ue(),{saveResult:Rt}=A(),{generateUuid:Lt,API_RESULT_URL:bt}=d(),{buildUserAgent:Ut}=fe(),O=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await At(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let n={testId:Lt(),timestamp:new Date().toISOString(),source:"sdk"},i,a;try{if((s.includes("download")||s.includes("upload"))&&(n.testServer=await Mt(),this._reportProgress({type:"info",message:`Test server: ${n.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let o=await _t({onProgress:r=>this._reportProgress(r)});n.latency=o.latency,n.jitter=o.jitter,this._reportProgress({type:"info",message:`Latency: ${n.latency}ms, Jitter: ${n.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let o=await Dt({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:r=>this._reportProgress(r)});n.download=o.download,n.maxDownload=o.maxDownload,i=o.lastSampleSize,a=o.lastSampleData,this._reportProgress({type:"info",message:`Download: ${n.download} Mbps (max: ${n.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let o=await Et({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:a,supportGbTest:this.supportGbTest,onProgress:r=>this._reportProgress(r)});n.upload=o.upload,n.maxUpload=o.maxUpload,this._reportProgress({type:"info",message:`Upload: ${n.upload} Mbps (max: ${n.maxUpload} Mbps)`})}return this.saveHistory&&Rt(n),await this._sendResult(n),n}catch(o){throw this.onError&&this.onError(o),o}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Ut(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(bt,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};he.exports=O});var Te=p((rs,xe)=>{var{Server:It}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:Ot}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:vt,ListToolsRequestSchema:kt,ListResourcesRequestSchema:$t,ReadResourceRequestSchema:qt}=require("@modelcontextprotocol/sdk/types.js"),Ct=v(),{getHistory:Se,getHistoryStats:Ft}=A(),{getVersion:ye}=d();async function Ht(){let t=process.env.SOM_API_SECRET;t||(console.error("Error: SOM_API_SECRET environment variable is required"),console.error("Get your API secret from: https://speedof.me/api/portal/"),process.exit(1));let e=ye(),s=new It({name:"speedofme",version:e},{capabilities:{tools:{},resources:{}}});s.setRequestHandler(kt,async()=>({tools:[{name:"run_speed_test",description:"Run an internet speed test to measure download speed, upload speed, latency, and jitter. Results are saved to local history.",inputSchema:{type:"object",properties:{tests:{type:"array",items:{type:"string",enum:["download","upload","latency"]},description:"Which tests to run. Default: all (latency, download, upload)"},sustainTime:{type:"number",minimum:1,maximum:8,description:"How long to sustain each download sample (1-8 seconds). Lower = faster but less accurate, higher = slower but more accurate. Default: 6"}},required:[]}},{name:"get_test_history",description:"Get historical speed test results from local storage.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of results to return. Default: 10"},since:{type:"string",description:"ISO date string. Return results after this time."}},required:[]}}]})),s.setRequestHandler(vt,async i=>{let{name:a,arguments:o}=i.params;try{if(a==="run_speed_test"){let c=await new Ct({apiSecret:t,sustainTime:o?.sustainTime||6,clientType:"mcp"}).run({tests:o?.tests||["latency","download","upload"]});return{content:[{type:"text",text:ge(c)}]}}else if(a==="get_test_history"){let r=Se({limit:o?.limit||10,since:o?.since});return{content:[{type:"text",text:we(r)}]}}else throw new Error(`Unknown tool: ${a}`)}catch(r){return{content:[{type:"text",text:`Error: ${r.message}`}],isError:!0}}}),s.setRequestHandler($t,async()=>({resources:[{uri:"speedofme://history",name:"Speed Test History",description:"Recent speed test results from local storage",mimeType:"application/json"},{uri:"speedofme://config",name:"SDK Configuration",description:"Current SDK configuration and status",mimeType:"application/json"}]})),s.setRequestHandler(qt,async i=>{let{uri:a}=i.params;if(a==="speedofme://history"){let o=Se({limit:50});return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify(o,null,2)}]}}else if(a==="speedofme://config"){let o=Ft(),r=ye();return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify({version:r,apiSecretConfigured:!!t,history:o},null,2)}]}}else throw new Error(`Unknown resource: ${a}`)});let n=new Ot;await s.connect(n),console.error("SpeedOf.Me MCP Server started")}function ge(t){let e=["## Speed Test Results","",`**Test ID:** ${t.testId}`,`**Timestamp:** ${t.timestamp}`,`**Test Server:** ${t.testServer||"Unknown"}`,""];return t.latency!==void 0&&(e.push(`**Latency:** ${t.latency} ms`),e.push(`**Jitter:** ${t.jitter} ms`)),t.download!==void 0&&e.push(`**Download:** ${t.download} Mbps (max: ${t.maxDownload} Mbps)`),t.upload!==void 0&&e.push(`**Upload:** ${t.upload} Mbps (max: ${t.maxUpload} Mbps)`),e.join(`
1
+ var u=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var q=u((Kt,Pe)=>{Pe.exports={name:"@speedofme/mcp",mcpName:"me.speedof/speed-test",version:"1.0.11",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=u((Vt,B)=>{var C=require("fs"),Ae=require("path"),_e=require("os"),R=Ae.join(_e.homedir(),".speedofme");function De(){C.existsSync(R)||C.mkdirSync(R,{recursive:!0})}var w="https://cdn3.speedof.me",Ee=`${w}/sf/`,Me=`${w}/ul`,Re=`${w}/location/`,Le=`${w}/location/pops.json`,F="https://api.speedof.me",be=`${F}/service/auth`,Ue=`${F}/service/result`,H="https://speedof.me/";async function Ie(t,e={}){let s={Referer:H,...e.headers};return fetch(t,{...e,headers:s})}var j=.1;function Oe(t,e){return e<1&&(e=1),N(t/e/125e3*(1+j))}function N(t){return Math.round(t*100)/100}function ve(){return Date.now()}function ke(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function $e(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function qe(){if(!T)try{T=q().version||"1.0.0"}catch{T="1.0.0"}return T}var Ce=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],Fe=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];B.exports={SPEEDOFME_DIR:R,ensureSpeedofmeDir:De,CDN_BASE:w,SAMPLE_FILES_PATH:Ee,UPLOAD_URL:Me,LOCATION_URL:Re,POPS_URL:Le,API_AUTH_URL:be,API_RESULT_URL:Ue,REFERER:H,cdnFetch:Ie,NET_LOSS:j,getSpeed:Oe,round:N,getCurrTime:ve,calcJitter:ke,generateUuid:$e,getVersion:qe,SAMPLES:Ce,GB_SAMPLES:Fe}});var L=u((Xt,z)=>{var{API_AUTH_URL:He}=d();async function je(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(He,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let n={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(n[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}z.exports={authenticate:je}});var K=u((Wt,Y)=>{var{getCurrTime:G,calcJitter:Ne,cdnFetch:Be,SAMPLE_FILES_PATH:ze}=d(),J=10;async function Ge(t={}){let{onProgress:e}=t,s=[];for(let a=0;a<J;a++){let o=G();try{await Be(`${ze}?r=${Math.random()}`,{method:"HEAD"});let r=G()-o;if(s.push(r>0?r:1),e&&e({type:"latency",pass:a+1,percentDone:Math.round((a+1)/J*100),currentLatency:s[a]}),r>999)break;await new Promise(c=>setTimeout(c,1))}catch{s.push(1e3)}}let n=Math.min(...s),i=Math.floor(Ne(s));return{latency:n,jitter:i}}Y.exports={runLatencyTest:Ge}});var Z=u((Zt,W)=>{var{getCurrTime:V,getSpeed:Je,cdnFetch:Ye,SAMPLES:X,GB_SAMPLES:Ke,SAMPLE_FILES_PATH:Ve}=d();async function Xe(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:n=0,onProgress:i}=t,a=s?[...X,...Ke]:[...X],o=n>0?Math.min(n,a.length):a.length,r=0,c=0,p=0,l=a[0][0],f=null;for(;r<o;){let[S,h]=a[r];r++,l=S;let _=`${Ve}${h}.bin?r=${Math.random()}`;try{let D=V(),E=await Ye(_);if(!E.ok)throw new Error(`HTTP ${E.status}`);let k=await E.arrayBuffer(),M=k.byteLength;f=Buffer.from(k);let $=(V()-D)/1e3;if(c=Je(M,$),c>p&&(p=c),i&&i({type:"download",pass:r,percentDone:100,bytesReceived:M,totalBytes:M,currentSpeed:c,maxSpeed:p}),$>=e)break}catch(D){if(r===1)throw new Error(`Download test failed: ${D.message}`);break}}return{download:c,maxDownload:p,passes:r,lastSampleSize:l,lastSampleData:f}}W.exports={runDownloadTest:Xe}});var oe=u((Qt,re)=>{var P=require("fs"),te=require("path"),{getCurrTime:Q,getSpeed:We,SPEEDOFME_DIR:se,ensureSpeedofmeDir:Ze,UPLOAD_URL:Qe,REFERER:et}=d(),ee=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],tt=[134217728,268435456],ne="upload-sample.bin",st=67108864,nt=268435456;async function rt(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:n,supportGbTest:i=!1,onProgress:a}=t,o,r,c;if(s&&n){let p=Math.min(s,536870912),l=p/2;o=n,r=[];for(let f=1;f<=10;f++){let S=Math.pow(2,5-f),h=Math.min(Math.floor(p/S),l);if((r.length===0||h>r[r.length-1])&&r.push(h),h>=l)break}c=l}else{let p=i?nt:st;r=i?[...ee,...tt]:ee,await at(p),o=P.readFileSync(te.join(se,ne)),c=null}return ot({dataBuffer:o,sampleSizes:r,maxSizeCheck:c,sustainTime:e,onProgress:a})}async function ot(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:n,sustainTime:i,onProgress:a}=t,o=0,r=0,c=0;for(let p of s){o++;try{let l=e.slice(0,p),f=Q(),S=await fetch(Qe,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:et},body:l});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(Q()-f)/1e3;r=We(p,h),r>c&&(c=r),a&&a({type:"upload",pass:o,percentDone:100,bytesSent:p,totalBytes:p,currentSpeed:r,maxSpeed:c});let _=n&&p>=n;if(h>=i||_)break}catch(l){if(o===1)throw new Error(`Upload test failed: ${l.message}`);break}}return{upload:r,maxUpload:c,passes:o}}async function at(t){let e=te.join(se,ne);if(Ze(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let n=0;n<t;n+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,n)}P.writeFileSync(e,s)}re.exports={runUploadTest:rt}});var pe=u((es,ce)=>{var{cdnFetch:ie,LOCATION_URL:it,POPS_URL:ct}=d(),g=null,x=null,ae={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function pt(){return g||x||(x=(async()=>{try{let t=await ie(ct);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||ae,g}catch{return g=ae,g}finally{x=null}})(),x)}async function ut(){try{let t=await pt(),e=await ie(`${it}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let n=s.toUpperCase();return t[n]||n}catch{return"Unknown"}}function lt(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ce.exports={getTestServer:ut,getPopName:lt}});var A=u((ts,ue)=>{var y=require("fs"),dt=require("path"),{SPEEDOFME_DIR:mt,ensureSpeedofmeDir:ft}=d(),m=dt.join(mt,"history.json"),b=1024*1024,ht=400;function U(){try{if(y.existsSync(m)){let t=y.readFileSync(m,"utf8");return JSON.parse(t)}}catch{}return[]}function St(t){ft();let e=Math.floor(b/ht);t.length>e&&(t=t.slice(-e)),y.writeFileSync(m,JSON.stringify(t,null,2),"utf8")}function yt(t={}){let{limit:e=10,since:s}=t,n=U();if(s){let i=new Date(s);n=n.filter(a=>new Date(a.timestamp)>=i)}return n.slice(-e).reverse()}function gt(t){try{let e=U();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),St(e),!0}catch{return!1}}function wt(){try{return y.existsSync(m)&&y.unlinkSync(m),!0}catch{return!1}}function xt(){try{let t=U(),e=y.existsSync(m)?y.statSync(m):{size:0};return{path:m,size:e.size,count:t.length,maxSize:b,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:m,size:0,count:0,maxSize:b,error:t.message}}}ue.exports={getHistory:yt,saveResult:gt,clearHistory:wt,getHistoryStats:xt,HISTORY_FILE:m}});var fe=u((ss,me)=>{var I=require("os"),{getVersion:Tt}=d();function le(){let t=I.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function de(){return I.release()}function Pt(t="sdk"){let e=Tt(),s=le(),n=de(),i=I.arch(),a=process.version;return`SpeedOfMe-${t}/${e} (${s} ${n}; ${i}) Node/${a}`}me.exports={buildUserAgent:Pt,getOSName:le,getOSVersion:de}});var v=u((ns,he)=>{var{authenticate:At}=L(),{runLatencyTest:_t}=K(),{runDownloadTest:Dt}=Z(),{runUploadTest:Et}=oe(),{getTestServer:Mt}=pe(),{saveResult:Rt}=A(),{generateUuid:Lt,API_RESULT_URL:bt}=d(),{buildUserAgent:Ut}=fe(),O=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await At(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let n={testId:Lt(),timestamp:new Date().toISOString(),source:"sdk"},i,a;try{if((s.includes("download")||s.includes("upload"))&&(n.testServer=await Mt(),this._reportProgress({type:"info",message:`Test server: ${n.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let o=await _t({onProgress:r=>this._reportProgress(r)});n.latency=o.latency,n.jitter=o.jitter,this._reportProgress({type:"info",message:`Latency: ${n.latency}ms, Jitter: ${n.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let o=await Dt({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:r=>this._reportProgress(r)});n.download=o.download,n.maxDownload=o.maxDownload,i=o.lastSampleSize,a=o.lastSampleData,this._reportProgress({type:"info",message:`Download: ${n.download} Mbps (max: ${n.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let o=await Et({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:a,supportGbTest:this.supportGbTest,onProgress:r=>this._reportProgress(r)});n.upload=o.upload,n.maxUpload=o.maxUpload,this._reportProgress({type:"info",message:`Upload: ${n.upload} Mbps (max: ${n.maxUpload} Mbps)`})}return this.saveHistory&&Rt(n),await this._sendResult(n),n}catch(o){throw this.onError&&this.onError(o),o}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Ut(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(bt,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};he.exports=O});var Te=u((rs,xe)=>{var{Server:It}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:Ot}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:vt,ListToolsRequestSchema:kt,ListResourcesRequestSchema:$t,ReadResourceRequestSchema:qt}=require("@modelcontextprotocol/sdk/types.js"),Ct=v(),{getHistory:Se,getHistoryStats:Ft}=A(),{getVersion:ye}=d();async function Ht(){let t=process.env.SOM_API_SECRET;t||(console.error("Error: SOM_API_SECRET environment variable is required"),console.error("Get your API secret from: https://speedof.me/api/portal/"),process.exit(1));let e=ye(),s=new It({name:"speedofme",version:e},{capabilities:{tools:{},resources:{}}});s.setRequestHandler(kt,async()=>({tools:[{name:"run_speed_test",description:"Run an internet speed test to measure download speed, upload speed, latency, and jitter. Results are saved to local history.",inputSchema:{type:"object",properties:{tests:{type:"array",items:{type:"string",enum:["download","upload","latency"]},description:"Which tests to run. Default: all (latency, download, upload)"},sustainTime:{type:"number",minimum:1,maximum:8,description:"How long to sustain each download sample (1-8 seconds). Lower = faster but less accurate, higher = slower but more accurate. Default: 6"}},required:[]}},{name:"get_test_history",description:"Get historical speed test results from local storage.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of results to return. Default: 10"},since:{type:"string",description:"ISO date string. Return results after this time."}},required:[]}}]})),s.setRequestHandler(vt,async i=>{let{name:a,arguments:o}=i.params;try{if(a==="run_speed_test"){let c=await new Ct({apiSecret:t,sustainTime:o?.sustainTime||6,clientType:"mcp"}).run({tests:o?.tests||["latency","download","upload"]});return{content:[{type:"text",text:ge(c)}]}}else if(a==="get_test_history"){let r=Se({limit:o?.limit||10,since:o?.since});return{content:[{type:"text",text:we(r)}]}}else throw new Error(`Unknown tool: ${a}`)}catch(r){return{content:[{type:"text",text:`Error: ${r.message}`}],isError:!0}}}),s.setRequestHandler($t,async()=>({resources:[{uri:"speedofme://history",name:"Speed Test History",description:"Recent speed test results from local storage",mimeType:"application/json"},{uri:"speedofme://config",name:"SDK Configuration",description:"Current SDK configuration and status",mimeType:"application/json"}]})),s.setRequestHandler(qt,async i=>{let{uri:a}=i.params;if(a==="speedofme://history"){let o=Se({limit:50});return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify(o,null,2)}]}}else if(a==="speedofme://config"){let o=Ft(),r=ye();return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify({version:r,apiSecretConfigured:!!t,history:o},null,2)}]}}else throw new Error(`Unknown resource: ${a}`)});let n=new Ot;await s.connect(n),console.error("SpeedOf.Me MCP Server started")}function ge(t){let e=["## Speed Test Results","",`**Test ID:** ${t.testId}`,`**Timestamp:** ${t.timestamp}`,`**Test Server:** ${t.testServer||"Unknown"}`,""];return t.latency!==void 0&&(e.push(`**Latency:** ${t.latency} ms`),e.push(`**Jitter:** ${t.jitter} ms`)),t.download!==void 0&&e.push(`**Download:** ${t.download} Mbps (max: ${t.maxDownload} Mbps)`),t.upload!==void 0&&e.push(`**Upload:** ${t.upload} Mbps (max: ${t.maxUpload} Mbps)`),e.join(`
2
2
  `)}function we(t){if(t.length===0)return"No test history found.";let e=["## Speed Test History","",`Found ${t.length} result(s):`,""];return t.forEach((s,n)=>{e.push(`### Test ${n+1}`),e.push(`- **Date:** ${s.timestamp}`),s.download&&e.push(`- **Download:** ${s.download} Mbps`),s.upload&&e.push(`- **Upload:** ${s.upload} Mbps`),s.latency&&e.push(`- **Latency:** ${s.latency} ms`),e.push("")}),e.join(`
3
3
  `)}xe.exports={startMcpServer:Ht,formatSpeedTestResult:ge,formatHistory:we}});var jt=v(),{authenticate:Nt}=L(),{getHistory:Bt,saveResult:zt,clearHistory:Gt}=A(),{startMcpServer:Jt}=Te();module.exports={SpeedTest:jt,authenticate:Nt,getHistory:Bt,saveResult:zt,clearHistory:Gt,startMcpServer:Jt};
package/dist/mcp.js CHANGED
@@ -1,3 +1,3 @@
1
- var l=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var v=l((Ht,xe)=>{xe.exports={name:"@speedofme/mcp",version:"1.0.10",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=l((jt,j)=>{var $=require("fs"),Te=require("path"),Pe=require("os"),M=Te.join(Pe.homedir(),".speedofme");function Ae(){$.existsSync(M)||$.mkdirSync(M,{recursive:!0})}var w="https://cdn3.speedof.me",_e=`${w}/sf/`,De=`${w}/ul`,Ee=`${w}/location/`,Me=`${w}/location/pops.json`,q="https://api.speedof.me",Re=`${q}/service/auth`,Le=`${q}/service/result`,C="https://speedof.me/";async function be(t,e={}){let s={Referer:C,...e.headers};return fetch(t,{...e,headers:s})}var F=.1;function Ue(t,e){return e<1&&(e=1),H(t/e/125e3*(1+F))}function H(t){return Math.round(t*100)/100}function Ie(){return Date.now()}function Oe(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function ke(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function ve(){if(!T)try{T=v().version||"1.0.0"}catch{T="1.0.0"}return T}var $e=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],qe=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];j.exports={SPEEDOFME_DIR:M,ensureSpeedofmeDir:Ae,CDN_BASE:w,SAMPLE_FILES_PATH:_e,UPLOAD_URL:De,LOCATION_URL:Ee,POPS_URL:Me,API_AUTH_URL:Re,API_RESULT_URL:Le,REFERER:C,cdnFetch:be,NET_LOSS:F,getSpeed:Ue,round:H,getCurrTime:Ie,calcJitter:Oe,generateUuid:ke,getVersion:ve,SAMPLES:$e,GB_SAMPLES:qe}});var B=l((Nt,N)=>{var{API_AUTH_URL:Ce}=d();async function Fe(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(Ce,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let n={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(n[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}N.exports={authenticate:Fe}});var Y=l((Bt,J)=>{var{getCurrTime:z,calcJitter:He,cdnFetch:je,SAMPLE_FILES_PATH:Ne}=d(),G=10;async function Be(t={}){let{onProgress:e}=t,s=[];for(let a=0;a<G;a++){let o=z();try{await je(`${Ne}?r=${Math.random()}`,{method:"HEAD"});let r=z()-o;if(s.push(r>0?r:1),e&&e({type:"latency",pass:a+1,percentDone:Math.round((a+1)/G*100),currentLatency:s[a]}),r>999)break;await new Promise(c=>setTimeout(c,1))}catch{s.push(1e3)}}let n=Math.min(...s),i=Math.floor(He(s));return{latency:n,jitter:i}}J.exports={runLatencyTest:Be}});var W=l((zt,X)=>{var{getCurrTime:K,getSpeed:ze,cdnFetch:Ge,SAMPLES:V,GB_SAMPLES:Je,SAMPLE_FILES_PATH:Ye}=d();async function Ke(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:n=0,onProgress:i}=t,a=s?[...V,...Je]:[...V],o=n>0?Math.min(n,a.length):a.length,r=0,c=0,p=0,u=a[0][0],f=null;for(;r<o;){let[S,h]=a[r];r++,u=S;let A=`${Ye}${h}.bin?r=${Math.random()}`;try{let _=K(),D=await Ge(A);if(!D.ok)throw new Error(`HTTP ${D.status}`);let O=await D.arrayBuffer(),E=O.byteLength;f=Buffer.from(O);let k=(K()-_)/1e3;if(c=ze(E,k),c>p&&(p=c),i&&i({type:"download",pass:r,percentDone:100,bytesReceived:E,totalBytes:E,currentSpeed:c,maxSpeed:p}),k>=e)break}catch(_){if(r===1)throw new Error(`Download test failed: ${_.message}`);break}}return{download:c,maxDownload:p,passes:r,lastSampleSize:u,lastSampleData:f}}X.exports={runDownloadTest:Ke}});var re=l((Gt,ne)=>{var P=require("fs"),ee=require("path"),{getCurrTime:Z,getSpeed:Ve,SPEEDOFME_DIR:te,ensureSpeedofmeDir:Xe,UPLOAD_URL:We,REFERER:Ze}=d(),Q=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],Qe=[134217728,268435456],se="upload-sample.bin",et=67108864,tt=268435456;async function st(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:n,supportGbTest:i=!1,onProgress:a}=t,o,r,c;if(s&&n){let p=Math.min(s,536870912),u=p/2;o=n,r=[];for(let f=1;f<=10;f++){let S=Math.pow(2,5-f),h=Math.min(Math.floor(p/S),u);if((r.length===0||h>r[r.length-1])&&r.push(h),h>=u)break}c=u}else{let p=i?tt:et;r=i?[...Q,...Qe]:Q,await rt(p),o=P.readFileSync(ee.join(te,se)),c=null}return nt({dataBuffer:o,sampleSizes:r,maxSizeCheck:c,sustainTime:e,onProgress:a})}async function nt(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:n,sustainTime:i,onProgress:a}=t,o=0,r=0,c=0;for(let p of s){o++;try{let u=e.slice(0,p),f=Z(),S=await fetch(We,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:Ze},body:u});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(Z()-f)/1e3;r=Ve(p,h),r>c&&(c=r),a&&a({type:"upload",pass:o,percentDone:100,bytesSent:p,totalBytes:p,currentSpeed:r,maxSpeed:c});let A=n&&p>=n;if(h>=i||A)break}catch(u){if(o===1)throw new Error(`Upload test failed: ${u.message}`);break}}return{upload:r,maxUpload:c,passes:o}}async function rt(t){let e=ee.join(te,se);if(Xe(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let n=0;n<t;n+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,n)}P.writeFileSync(e,s)}ne.exports={runUploadTest:st}});var ce=l((Jt,ie)=>{var{cdnFetch:ae,LOCATION_URL:ot,POPS_URL:at}=d(),g=null,x=null,oe={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function it(){return g||x||(x=(async()=>{try{let t=await ae(at);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||oe,g}catch{return g=oe,g}finally{x=null}})(),x)}async function ct(){try{let t=await it(),e=await ae(`${ot}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let n=s.toUpperCase();return t[n]||n}catch{return"Unknown"}}function pt(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ie.exports={getTestServer:ct,getPopName:pt}});var b=l((Yt,pe)=>{var y=require("fs"),ut=require("path"),{SPEEDOFME_DIR:lt,ensureSpeedofmeDir:dt}=d(),m=ut.join(lt,"history.json"),R=1024*1024,mt=400;function L(){try{if(y.existsSync(m)){let t=y.readFileSync(m,"utf8");return JSON.parse(t)}}catch{}return[]}function ft(t){dt();let e=Math.floor(R/mt);t.length>e&&(t=t.slice(-e)),y.writeFileSync(m,JSON.stringify(t,null,2),"utf8")}function ht(t={}){let{limit:e=10,since:s}=t,n=L();if(s){let i=new Date(s);n=n.filter(a=>new Date(a.timestamp)>=i)}return n.slice(-e).reverse()}function St(t){try{let e=L();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),ft(e),!0}catch{return!1}}function yt(){try{return y.existsSync(m)&&y.unlinkSync(m),!0}catch{return!1}}function gt(){try{let t=L(),e=y.existsSync(m)?y.statSync(m):{size:0};return{path:m,size:e.size,count:t.length,maxSize:R,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:m,size:0,count:0,maxSize:R,error:t.message}}}pe.exports={getHistory:ht,saveResult:St,clearHistory:yt,getHistoryStats:gt,HISTORY_FILE:m}});var me=l((Kt,de)=>{var U=require("os"),{getVersion:wt}=d();function ue(){let t=U.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function le(){return U.release()}function xt(t="sdk"){let e=wt(),s=ue(),n=le(),i=U.arch(),a=process.version;return`SpeedOfMe-${t}/${e} (${s} ${n}; ${i}) Node/${a}`}de.exports={buildUserAgent:xt,getOSName:ue,getOSVersion:le}});var he=l((Vt,fe)=>{var{authenticate:Tt}=B(),{runLatencyTest:Pt}=Y(),{runDownloadTest:At}=W(),{runUploadTest:_t}=re(),{getTestServer:Dt}=ce(),{saveResult:Et}=b(),{generateUuid:Mt,API_RESULT_URL:Rt}=d(),{buildUserAgent:Lt}=me(),I=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await Tt(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let n={testId:Mt(),timestamp:new Date().toISOString(),source:"sdk"},i,a;try{if((s.includes("download")||s.includes("upload"))&&(n.testServer=await Dt(),this._reportProgress({type:"info",message:`Test server: ${n.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let o=await Pt({onProgress:r=>this._reportProgress(r)});n.latency=o.latency,n.jitter=o.jitter,this._reportProgress({type:"info",message:`Latency: ${n.latency}ms, Jitter: ${n.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let o=await At({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:r=>this._reportProgress(r)});n.download=o.download,n.maxDownload=o.maxDownload,i=o.lastSampleSize,a=o.lastSampleData,this._reportProgress({type:"info",message:`Download: ${n.download} Mbps (max: ${n.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let o=await _t({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:a,supportGbTest:this.supportGbTest,onProgress:r=>this._reportProgress(r)});n.upload=o.upload,n.maxUpload=o.maxUpload,this._reportProgress({type:"info",message:`Upload: ${n.upload} Mbps (max: ${n.maxUpload} Mbps)`})}return this.saveHistory&&Et(n),await this._sendResult(n),n}catch(o){throw this.onError&&this.onError(o),o}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Lt(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(Rt,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};fe.exports=I});var{Server:bt}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:Ut}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:It,ListToolsRequestSchema:Ot,ListResourcesRequestSchema:kt,ReadResourceRequestSchema:vt}=require("@modelcontextprotocol/sdk/types.js"),$t=he(),{getHistory:Se,getHistoryStats:qt}=b(),{getVersion:ye}=d();async function Ct(){let t=process.env.SOM_API_SECRET;t||(console.error("Error: SOM_API_SECRET environment variable is required"),console.error("Get your API secret from: https://speedof.me/api/portal/"),process.exit(1));let e=ye(),s=new bt({name:"speedofme",version:e},{capabilities:{tools:{},resources:{}}});s.setRequestHandler(Ot,async()=>({tools:[{name:"run_speed_test",description:"Run an internet speed test to measure download speed, upload speed, latency, and jitter. Results are saved to local history.",inputSchema:{type:"object",properties:{tests:{type:"array",items:{type:"string",enum:["download","upload","latency"]},description:"Which tests to run. Default: all (latency, download, upload)"},sustainTime:{type:"number",minimum:1,maximum:8,description:"How long to sustain each download sample (1-8 seconds). Lower = faster but less accurate, higher = slower but more accurate. Default: 6"}},required:[]}},{name:"get_test_history",description:"Get historical speed test results from local storage.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of results to return. Default: 10"},since:{type:"string",description:"ISO date string. Return results after this time."}},required:[]}}]})),s.setRequestHandler(It,async i=>{let{name:a,arguments:o}=i.params;try{if(a==="run_speed_test"){let c=await new $t({apiSecret:t,sustainTime:o?.sustainTime||6,clientType:"mcp"}).run({tests:o?.tests||["latency","download","upload"]});return{content:[{type:"text",text:ge(c)}]}}else if(a==="get_test_history"){let r=Se({limit:o?.limit||10,since:o?.since});return{content:[{type:"text",text:we(r)}]}}else throw new Error(`Unknown tool: ${a}`)}catch(r){return{content:[{type:"text",text:`Error: ${r.message}`}],isError:!0}}}),s.setRequestHandler(kt,async()=>({resources:[{uri:"speedofme://history",name:"Speed Test History",description:"Recent speed test results from local storage",mimeType:"application/json"},{uri:"speedofme://config",name:"SDK Configuration",description:"Current SDK configuration and status",mimeType:"application/json"}]})),s.setRequestHandler(vt,async i=>{let{uri:a}=i.params;if(a==="speedofme://history"){let o=Se({limit:50});return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify(o,null,2)}]}}else if(a==="speedofme://config"){let o=qt(),r=ye();return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify({version:r,apiSecretConfigured:!!t,history:o},null,2)}]}}else throw new Error(`Unknown resource: ${a}`)});let n=new Ut;await s.connect(n),console.error("SpeedOf.Me MCP Server started")}function ge(t){let e=["## Speed Test Results","",`**Test ID:** ${t.testId}`,`**Timestamp:** ${t.timestamp}`,`**Test Server:** ${t.testServer||"Unknown"}`,""];return t.latency!==void 0&&(e.push(`**Latency:** ${t.latency} ms`),e.push(`**Jitter:** ${t.jitter} ms`)),t.download!==void 0&&e.push(`**Download:** ${t.download} Mbps (max: ${t.maxDownload} Mbps)`),t.upload!==void 0&&e.push(`**Upload:** ${t.upload} Mbps (max: ${t.maxUpload} Mbps)`),e.join(`
1
+ var l=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var v=l((Ht,xe)=>{xe.exports={name:"@speedofme/mcp",mcpName:"me.speedof/speed-test",version:"1.0.11",description:"SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",main:"dist/index.js",bin:{mcp:"bin/speedofme-mcp",speedofme:"bin/speedofme-mcp"},scripts:{build:"node build.js",prepublishOnly:"npm run build",test:"node --test src/*.test.js src/**/*.test.js","test:unit":"node --test src/*.test.js src/**/*.test.js",start:"node bin/speedofme-mcp"},keywords:["speedtest","speed-test","internet-speed","bandwidth","network","mcp","model-context-protocol","ai","claude"],author:"SpeedOf.Me <contact@speedof.me>",license:"UNLICENSED",private:!1,engines:{node:">=18.0.0"},dependencies:{"@modelcontextprotocol/sdk":"^1.0.0"},homepage:"https://speedof.me/api",devDependencies:{esbuild:"^0.27.2"}}});var d=l((jt,j)=>{var $=require("fs"),Te=require("path"),Pe=require("os"),M=Te.join(Pe.homedir(),".speedofme");function Ae(){$.existsSync(M)||$.mkdirSync(M,{recursive:!0})}var w="https://cdn3.speedof.me",_e=`${w}/sf/`,De=`${w}/ul`,Ee=`${w}/location/`,Me=`${w}/location/pops.json`,q="https://api.speedof.me",Re=`${q}/service/auth`,Le=`${q}/service/result`,C="https://speedof.me/";async function be(t,e={}){let s={Referer:C,...e.headers};return fetch(t,{...e,headers:s})}var F=.1;function Ue(t,e){return e<1&&(e=1),H(t/e/125e3*(1+F))}function H(t){return Math.round(t*100)/100}function Ie(){return Date.now()}function Oe(t){if(t.length<2)return 0;let e=0;for(let s=0;s<t.length-1;s++)e+=Math.abs(t[s]-t[s+1]);return e/(t.length-1)}function ke(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}var T=null;function ve(){if(!T)try{T=v().version||"1.0.0"}catch{T="1.0.0"}return T}var $e=[[131072,"sample128k"],[262144,"sample256k"],[524288,"sample512k"],[1048576,"sample1024k"],[2097152,"sample2048k"],[4194304,"sample4096k"],[8388608,"sample8192k"],[16777216,"sample16384k"],[33554432,"sample32768k"],[67108864,"sample65536k"],[134217728,"sample131072k"]],qe=[[268435456,"sample256m"],[536870912,"sample512m"],[1073741824,"sample1024m"]];j.exports={SPEEDOFME_DIR:M,ensureSpeedofmeDir:Ae,CDN_BASE:w,SAMPLE_FILES_PATH:_e,UPLOAD_URL:De,LOCATION_URL:Ee,POPS_URL:Me,API_AUTH_URL:Re,API_RESULT_URL:Le,REFERER:C,cdnFetch:be,NET_LOSS:F,getSpeed:Ue,round:H,getCurrTime:Ie,calcJitter:Oe,generateUuid:ke,getVersion:ve,SAMPLES:$e,GB_SAMPLES:qe}});var B=l((Nt,N)=>{var{API_AUTH_URL:Ce}=d();async function Fe(t){if(!t)throw new Error("API secret is required");if(!t.startsWith("SOM_SECRET_")||t.length<40)throw new Error("Invalid API secret format. Expected: SOM_SECRET_xxx");let s=await(await fetch(Ce,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiSecret:t})})).json();if(s.code!==1e3){let n={1001:"Invalid API secret",1003:"Account not active",1500:"Server error"};throw new Error(n[s.code]||s.message||"Authentication failed")}return{userId:s.userId,token:s.token}}N.exports={authenticate:Fe}});var Y=l((Bt,J)=>{var{getCurrTime:z,calcJitter:He,cdnFetch:je,SAMPLE_FILES_PATH:Ne}=d(),G=10;async function Be(t={}){let{onProgress:e}=t,s=[];for(let a=0;a<G;a++){let o=z();try{await je(`${Ne}?r=${Math.random()}`,{method:"HEAD"});let r=z()-o;if(s.push(r>0?r:1),e&&e({type:"latency",pass:a+1,percentDone:Math.round((a+1)/G*100),currentLatency:s[a]}),r>999)break;await new Promise(c=>setTimeout(c,1))}catch{s.push(1e3)}}let n=Math.min(...s),i=Math.floor(He(s));return{latency:n,jitter:i}}J.exports={runLatencyTest:Be}});var W=l((zt,X)=>{var{getCurrTime:K,getSpeed:ze,cdnFetch:Ge,SAMPLES:V,GB_SAMPLES:Je,SAMPLE_FILES_PATH:Ye}=d();async function Ke(t={}){let{sustainTime:e=6,supportGbTest:s=!1,maxTestPass:n=0,onProgress:i}=t,a=s?[...V,...Je]:[...V],o=n>0?Math.min(n,a.length):a.length,r=0,c=0,p=0,u=a[0][0],f=null;for(;r<o;){let[S,h]=a[r];r++,u=S;let A=`${Ye}${h}.bin?r=${Math.random()}`;try{let _=K(),D=await Ge(A);if(!D.ok)throw new Error(`HTTP ${D.status}`);let O=await D.arrayBuffer(),E=O.byteLength;f=Buffer.from(O);let k=(K()-_)/1e3;if(c=ze(E,k),c>p&&(p=c),i&&i({type:"download",pass:r,percentDone:100,bytesReceived:E,totalBytes:E,currentSpeed:c,maxSpeed:p}),k>=e)break}catch(_){if(r===1)throw new Error(`Download test failed: ${_.message}`);break}}return{download:c,maxDownload:p,passes:r,lastSampleSize:u,lastSampleData:f}}X.exports={runDownloadTest:Ke}});var re=l((Gt,ne)=>{var P=require("fs"),ee=require("path"),{getCurrTime:Z,getSpeed:Ve,SPEEDOFME_DIR:te,ensureSpeedofmeDir:Xe,UPLOAD_URL:We,REFERER:Ze}=d(),Q=[131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864],Qe=[134217728,268435456],se="upload-sample.bin",et=67108864,tt=268435456;async function st(t={}){let{sustainTime:e=6,lastDownloadSize:s,lastDownloadData:n,supportGbTest:i=!1,onProgress:a}=t,o,r,c;if(s&&n){let p=Math.min(s,536870912),u=p/2;o=n,r=[];for(let f=1;f<=10;f++){let S=Math.pow(2,5-f),h=Math.min(Math.floor(p/S),u);if((r.length===0||h>r[r.length-1])&&r.push(h),h>=u)break}c=u}else{let p=i?tt:et;r=i?[...Q,...Qe]:Q,await rt(p),o=P.readFileSync(ee.join(te,se)),c=null}return nt({dataBuffer:o,sampleSizes:r,maxSizeCheck:c,sustainTime:e,onProgress:a})}async function nt(t){let{dataBuffer:e,sampleSizes:s,maxSizeCheck:n,sustainTime:i,onProgress:a}=t,o=0,r=0,c=0;for(let p of s){o++;try{let u=e.slice(0,p),f=Z(),S=await fetch(We,{method:"POST",headers:{"Content-Type":"application/octet-stream",Referer:Ze},body:u});if(!S.ok&&S.status!==204)throw new Error(`HTTP ${S.status}`);let h=(Z()-f)/1e3;r=Ve(p,h),r>c&&(c=r),a&&a({type:"upload",pass:o,percentDone:100,bytesSent:p,totalBytes:p,currentSpeed:r,maxSpeed:c});let A=n&&p>=n;if(h>=i||A)break}catch(u){if(o===1)throw new Error(`Upload test failed: ${u.message}`);break}}return{upload:r,maxUpload:c,passes:o}}async function rt(t){let e=ee.join(te,se);if(Xe(),P.existsSync(e)&&P.statSync(e).size>=t)return;let s=Buffer.alloc(t);for(let n=0;n<t;n+=4){let i=Math.random()*4294967295>>>0;s.writeUInt32LE(i,n)}P.writeFileSync(e,s)}ne.exports={runUploadTest:st}});var ce=l((Jt,ie)=>{var{cdnFetch:ae,LOCATION_URL:ot,POPS_URL:at}=d(),g=null,x=null,oe={BOS:"Boston",CHI:"Chicago 1",DCA:"Ashburn 1",DEN:"Denver",DFW:"Dallas 1",FRA:"Frankfurt 1",HKG:"Hong Kong",IAD:"Ashburn 2",LAX:"Los Angeles 1",LHR:"London 1",MIA:"Miami",NRT:"Tokyo 1",ORD:"Chicago 2",PAO:"Palo Alto",PAR:"Paris",SEA:"Seattle",SIN:"Singapore 1",SJC:"San Jose 1",SYD:"Sydney 1",TYO:"Tokyo 2",YYZ:"Toronto"};async function it(){return g||x||(x=(async()=>{try{let t=await ae(at);if(!t.ok)throw new Error(`HTTP ${t.status}`);return g=(await t.json()).mapping||oe,g}catch{return g=oe,g}finally{x=null}})(),x)}async function ct(){try{let t=await it(),e=await ae(`${ot}?r=${Math.random()}`,{method:"HEAD"}),s=e.headers.get("SoM-Test-Server")||e.headers.get("som-test-server")||e.headers.get("x-som-test-server");if(!s)return"Unknown";let n=s.toUpperCase();return t[n]||n}catch{return"Unknown"}}function pt(t){if(!t)return"Unknown";let e=t.toUpperCase();return{SJC:"San Jose, CA",LAX:"Los Angeles, CA",SEA:"Seattle, WA",DEN:"Denver, CO",DFW:"Dallas, TX",ORD:"Chicago, IL",MIA:"Miami, FL",IAD:"Ashburn, VA",DCA:"Ashburn, VA",BOS:"Boston, MA",YYZ:"Toronto, CA",LHR:"London, UK",FRA:"Frankfurt, DE",PAR:"Paris, FR",AMS:"Amsterdam, NL",NRT:"Tokyo, JP",TYO:"Tokyo, JP",HKG:"Hong Kong",SIN:"Singapore",SYD:"Sydney, AU",MEL:"Melbourne, AU"}[e]||e}ie.exports={getTestServer:ct,getPopName:pt}});var b=l((Yt,pe)=>{var y=require("fs"),ut=require("path"),{SPEEDOFME_DIR:lt,ensureSpeedofmeDir:dt}=d(),m=ut.join(lt,"history.json"),R=1024*1024,mt=400;function L(){try{if(y.existsSync(m)){let t=y.readFileSync(m,"utf8");return JSON.parse(t)}}catch{}return[]}function ft(t){dt();let e=Math.floor(R/mt);t.length>e&&(t=t.slice(-e)),y.writeFileSync(m,JSON.stringify(t,null,2),"utf8")}function ht(t={}){let{limit:e=10,since:s}=t,n=L();if(s){let i=new Date(s);n=n.filter(a=>new Date(a.timestamp)>=i)}return n.slice(-e).reverse()}function St(t){try{let e=L();return t.timestamp||(t.timestamp=new Date().toISOString()),e.push(t),ft(e),!0}catch{return!1}}function yt(){try{return y.existsSync(m)&&y.unlinkSync(m),!0}catch{return!1}}function gt(){try{let t=L(),e=y.existsSync(m)?y.statSync(m):{size:0};return{path:m,size:e.size,count:t.length,maxSize:R,oldestResult:t.length>0?t[0].timestamp:null,newestResult:t.length>0?t[t.length-1].timestamp:null}}catch(t){return{path:m,size:0,count:0,maxSize:R,error:t.message}}}pe.exports={getHistory:ht,saveResult:St,clearHistory:yt,getHistoryStats:gt,HISTORY_FILE:m}});var me=l((Kt,de)=>{var U=require("os"),{getVersion:wt}=d();function ue(){let t=U.platform();switch(t){case"darwin":return"macOS";case"win32":return"Windows";case"linux":return"Linux";case"freebsd":return"FreeBSD";case"openbsd":return"OpenBSD";case"sunos":return"SunOS";case"aix":return"AIX";default:return t}}function le(){return U.release()}function xt(t="sdk"){let e=wt(),s=ue(),n=le(),i=U.arch(),a=process.version;return`SpeedOfMe-${t}/${e} (${s} ${n}; ${i}) Node/${a}`}de.exports={buildUserAgent:xt,getOSName:ue,getOSVersion:le}});var he=l((Vt,fe)=>{var{authenticate:Tt}=B(),{runLatencyTest:Pt}=Y(),{runDownloadTest:At}=W(),{runUploadTest:_t}=re(),{getTestServer:Dt}=ce(),{saveResult:Et}=b(),{generateUuid:Mt,API_RESULT_URL:Rt}=d(),{buildUserAgent:Lt}=me(),I=class{constructor(e={}){if(!e.apiSecret)throw new Error("API secret is required. Get one from speedof.me/api/portal/");this.apiSecret=e.apiSecret,this.clientType=e.clientType||"sdk";let s=typeof e.sustainTime=="number"?e.sustainTime:6;this.sustainTime=Math.max(1,Math.min(8,s)),this.supportGbTest=!!e.supportGbTest,this.maxTestPass=e.maxTestPass||0,this.saveHistory=e.saveHistory!==!1,this.onProgress=null,this.onError=null,this._authenticated=!1,this._userId=null,this._token=null}async authenticate(){let e=await Tt(this.apiSecret);this._userId=e.userId,this._token=e.token,this._authenticated=!0}async run(e={}){let s=e.tests||["latency","download","upload"];this._authenticated||await this.authenticate();let n={testId:Mt(),timestamp:new Date().toISOString(),source:"sdk"},i,a;try{if((s.includes("download")||s.includes("upload"))&&(n.testServer=await Dt(),this._reportProgress({type:"info",message:`Test server: ${n.testServer}`})),s.includes("latency")){this._reportProgress({type:"info",message:"Starting latency test..."});let o=await Pt({onProgress:r=>this._reportProgress(r)});n.latency=o.latency,n.jitter=o.jitter,this._reportProgress({type:"info",message:`Latency: ${n.latency}ms, Jitter: ${n.jitter}ms`})}if(s.includes("download")){this._reportProgress({type:"info",message:"Starting download test..."});let o=await At({sustainTime:this.sustainTime,supportGbTest:this.supportGbTest,maxTestPass:this.maxTestPass,onProgress:r=>this._reportProgress(r)});n.download=o.download,n.maxDownload=o.maxDownload,i=o.lastSampleSize,a=o.lastSampleData,this._reportProgress({type:"info",message:`Download: ${n.download} Mbps (max: ${n.maxDownload} Mbps)`})}if(s.includes("upload")){this._reportProgress({type:"info",message:"Starting upload test..."});let o=await _t({sustainTime:this.sustainTime,lastDownloadSize:i,lastDownloadData:a,supportGbTest:this.supportGbTest,onProgress:r=>this._reportProgress(r)});n.upload=o.upload,n.maxUpload=o.maxUpload,this._reportProgress({type:"info",message:`Upload: ${n.upload} Mbps (max: ${n.maxUpload} Mbps)`})}return this.saveHistory&&Et(n),await this._sendResult(n),n}catch(o){throw this.onError&&this.onError(o),o}}_reportProgress(e){this.onProgress&&this.onProgress(e)}async _sendResult(e){if(this._token)try{let s={testDate:e.timestamp,download:e.download||0,maxDownload:e.maxDownload||0,userAgent:Lt(this.clientType),client_type:this.clientType.toLowerCase()};e.upload&&(s.upload=e.upload),e.maxUpload&&(s.maxUpload=e.maxUpload),e.jitter&&(s.jitter=e.jitter),e.latency&&(s.latency=e.latency),e.testServer&&(s.testServer=e.testServer),await fetch(Rt,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._token}`},body:JSON.stringify(s)})}catch{}}};fe.exports=I});var{Server:bt}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:Ut}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:It,ListToolsRequestSchema:Ot,ListResourcesRequestSchema:kt,ReadResourceRequestSchema:vt}=require("@modelcontextprotocol/sdk/types.js"),$t=he(),{getHistory:Se,getHistoryStats:qt}=b(),{getVersion:ye}=d();async function Ct(){let t=process.env.SOM_API_SECRET;t||(console.error("Error: SOM_API_SECRET environment variable is required"),console.error("Get your API secret from: https://speedof.me/api/portal/"),process.exit(1));let e=ye(),s=new bt({name:"speedofme",version:e},{capabilities:{tools:{},resources:{}}});s.setRequestHandler(Ot,async()=>({tools:[{name:"run_speed_test",description:"Run an internet speed test to measure download speed, upload speed, latency, and jitter. Results are saved to local history.",inputSchema:{type:"object",properties:{tests:{type:"array",items:{type:"string",enum:["download","upload","latency"]},description:"Which tests to run. Default: all (latency, download, upload)"},sustainTime:{type:"number",minimum:1,maximum:8,description:"How long to sustain each download sample (1-8 seconds). Lower = faster but less accurate, higher = slower but more accurate. Default: 6"}},required:[]}},{name:"get_test_history",description:"Get historical speed test results from local storage.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of results to return. Default: 10"},since:{type:"string",description:"ISO date string. Return results after this time."}},required:[]}}]})),s.setRequestHandler(It,async i=>{let{name:a,arguments:o}=i.params;try{if(a==="run_speed_test"){let c=await new $t({apiSecret:t,sustainTime:o?.sustainTime||6,clientType:"mcp"}).run({tests:o?.tests||["latency","download","upload"]});return{content:[{type:"text",text:ge(c)}]}}else if(a==="get_test_history"){let r=Se({limit:o?.limit||10,since:o?.since});return{content:[{type:"text",text:we(r)}]}}else throw new Error(`Unknown tool: ${a}`)}catch(r){return{content:[{type:"text",text:`Error: ${r.message}`}],isError:!0}}}),s.setRequestHandler(kt,async()=>({resources:[{uri:"speedofme://history",name:"Speed Test History",description:"Recent speed test results from local storage",mimeType:"application/json"},{uri:"speedofme://config",name:"SDK Configuration",description:"Current SDK configuration and status",mimeType:"application/json"}]})),s.setRequestHandler(vt,async i=>{let{uri:a}=i.params;if(a==="speedofme://history"){let o=Se({limit:50});return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify(o,null,2)}]}}else if(a==="speedofme://config"){let o=qt(),r=ye();return{contents:[{uri:a,mimeType:"application/json",text:JSON.stringify({version:r,apiSecretConfigured:!!t,history:o},null,2)}]}}else throw new Error(`Unknown resource: ${a}`)});let n=new Ut;await s.connect(n),console.error("SpeedOf.Me MCP Server started")}function ge(t){let e=["## Speed Test Results","",`**Test ID:** ${t.testId}`,`**Timestamp:** ${t.timestamp}`,`**Test Server:** ${t.testServer||"Unknown"}`,""];return t.latency!==void 0&&(e.push(`**Latency:** ${t.latency} ms`),e.push(`**Jitter:** ${t.jitter} ms`)),t.download!==void 0&&e.push(`**Download:** ${t.download} Mbps (max: ${t.maxDownload} Mbps)`),t.upload!==void 0&&e.push(`**Upload:** ${t.upload} Mbps (max: ${t.maxUpload} Mbps)`),e.join(`
2
2
  `)}function we(t){if(t.length===0)return"No test history found.";let e=["## Speed Test History","",`Found ${t.length} result(s):`,""];return t.forEach((s,n)=>{e.push(`### Test ${n+1}`),e.push(`- **Date:** ${s.timestamp}`),s.download&&e.push(`- **Download:** ${s.download} Mbps`),s.upload&&e.push(`- **Upload:** ${s.upload} Mbps`),s.latency&&e.push(`- **Latency:** ${s.latency} ms`),e.push("")}),e.join(`
3
3
  `)}module.exports={startMcpServer:Ct,formatSpeedTestResult:ge,formatHistory:we};
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@speedofme/mcp",
3
- "version": "1.0.10",
3
+ "mcpName": "me.speedof/speed-test",
4
+ "version": "1.0.11",
4
5
  "description": "SpeedOf.Me MCP Server - Run accurate internet speed tests from AI agents. Also works as CLI and SDK.",
5
6
  "main": "dist/index.js",
6
7
  "bin": {
package/server.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "me.speedof/speed-test",
4
+ "description": "Official SpeedOf.Me server - Accurate speed tests via 129 global edge servers with analytics.",
5
+ "version": "1.0.10",
6
+ "packages": [
7
+ {
8
+ "registryType": "npm",
9
+ "identifier": "@speedofme/mcp",
10
+ "version": "1.0.10",
11
+ "transport": {
12
+ "type": "stdio"
13
+ },
14
+ "environmentVariables": [
15
+ {
16
+ "name": "SOM_API_SECRET",
17
+ "description": "Your SpeedOf.Me API secret from https://speedof.me/api/portal/settings.php",
18
+ "isRequired": true,
19
+ "format": "string",
20
+ "isSecret": true
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }