mcp-rustdoc 4.0.2 → 5.0.0

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.
Files changed (2) hide show
  1. package/dist/index.js +67 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,26 +1,71 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer as lt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ut}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as g}from"zod";import{load as X}from"cheerio";const dt=300*1e3,ht=900*1e3,pt=500,D=new Map;function F(e){const t=D.get(e);if(!t)return null;const o=Date.now();return o>t.staleExpiry?(D.delete(e),null):(t.lastAccess=o,t.data)}function ft(e){const t=D.get(e);return t?Date.now()>t.expiry:!0}function P(e,t,o=dt){for(;D.size>=pt;)mt();const s=Date.now();D.set(e,{data:t,expiry:s+o,staleExpiry:s+ht,lastAccess:s})}function mt(){let e=null,t=1/0;for(const[o,s]of D)s.lastAccess<t&&(t=s.lastAccess,e=o);e&&D.delete(e)}const et="https://docs.rs",K="https://crates.io/api/v1",st="mcp-rust-docs/4.0.0",ot=6e3,V=100,H={modules:"mod",structs:"struct",enums:"enum",traits:"trait",functions:"fn",macros:"macro",types:"type",constants:"constant",statics:"static",unions:"union",attributes:"attr",derives:"derive",reexports:"reexport"},Y={struct:"struct.",enum:"enum.",trait:"trait.",fn:"fn.",macro:"macro.",type:"type.",constant:"constant.",static:"static.",union:"union.",attr:"attr.",derive:"derive."},$t=new Set(["std","core","alloc"]);function G(e){return $t.has(e)}function gt(e,t){return`https://doc.rust-lang.org/stable/${e}/${t}`}function nt(e){return e.replace(/-/g,"_")}function j(e,t="index.html",o="latest"){return G(e)?gt(e,t):`${et}/${e}/${o}/${nt(e)}/${t}`}function W(e){return e?e.replace(/\./g,"/")+"/":""}function rt(e){return e?e.replace(/\./g,"::")+"::":""}class J extends Error{constructor(t,o){super(o),this.status=t}}async function q(e,t=2,o=500){for(let s=0;s<=t;s++)try{return await e()}catch(n){if(s===t||!(n instanceof J&&n.status>=500))throw n;console.log(`[retry ${s+1}/${t}] ${n.message}`),await new Promise(l=>setTimeout(l,o*(s+1)))}throw new Error("unreachable")}async function tt(e,t=15e3){const o=new AbortController,s=setTimeout(()=>o.abort(),t);try{const n=await fetch(e,{headers:{"User-Agent":st},signal:o.signal});if(!n.ok)throw new J(n.status,`HTTP ${n.status} for ${e}`);return await n.text()}finally{clearTimeout(s)}}async function U(e,t=1e4){const o=new AbortController,s=setTimeout(()=>o.abort(),t);try{const n=await fetch(e,{headers:{"User-Agent":st},signal:o.signal});if(!n.ok)throw new J(n.status,`HTTP ${n.status} for ${e}`);return await n.json()}finally{clearTimeout(s)}}async function I(e){const t=`dom:${e}`,o=F(t);if(o)return console.log(`[cache hit] ${e}`),ft(t)&&(console.log(`[stale refresh] ${e}`),q(()=>tt(e)).then(n=>P(t,n)).catch(()=>{})),X(o);const s=await q(()=>tt(e));return P(t,s),X(s)}function z(e){const t=X(`<body>${e}</body>`);return t("img").remove(),t("summary.hideme").remove(),t("p, div, br, h1, h2, h3, h4, h5, h6, li, tr").each((o,s)=>{t(s).before(`
2
+ import{McpServer as pt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ft}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as y}from"zod";import{load as Z}from"cheerio";const mt=300*1e3,$t=900*1e3,gt=500,O=new Map;function q(s){const t=O.get(s);if(!t)return null;const n=Date.now();return n>t.staleExpiry?(O.delete(s),null):(t.lastAccess=n,t.data)}function yt(s){const t=O.get(s);return t?Date.now()>t.expiry:!0}function M(s,t,n=mt){for(;O.size>=gt;)bt();const e=Date.now();O.set(s,{data:t,expiry:e+n,staleExpiry:e+$t,lastAccess:e})}function bt(){let s=null,t=1/0;for(const[n,e]of O)e.lastAccess<t&&(t=e.lastAccess,s=n);s&&O.delete(s)}const rt="https://docs.rs",B="https://crates.io/api/v1",at="mcp-rust-docs/5.0.0",it=6e3,G=100,W={modules:"mod",structs:"struct",enums:"enum",traits:"trait",functions:"fn",macros:"macro",types:"type",constants:"constant",statics:"static",unions:"union",attributes:"attr",derives:"derive",reexports:"reexport"},J={struct:"struct.",enum:"enum.",trait:"trait.",fn:"fn.",macro:"macro.",type:"type.",constant:"constant.",static:"static.",union:"union.",attr:"attr.",derive:"derive."},xt=new Set(["std","core","alloc"]);function F(s){return xt.has(s)}function wt(s,t){return`https://doc.rust-lang.org/stable/${s}/${t}`}function ct(s){return s.replace(/-/g,"_")}function j(s,t="index.html",n="latest"){return F(s)?wt(s,t):`${rt}/${s}/${n}/${ct(s)}/${t}`}function V(s){return s?s.replace(/\./g,"/")+"/":""}function N(s){return s?s.replace(/\./g,"::")+"::":""}class tt extends Error{constructor(t,n){super(n),this.status=t}}async function K(s,t=2,n=500){for(let e=0;e<=t;e++)try{return await s()}catch(o){if(e===t||!(o instanceof tt&&o.status>=500))throw o;console.log(`[retry ${e+1}/${t}] ${o.message}`),await new Promise(l=>setTimeout(l,n*(e+1)))}throw new Error("unreachable")}async function nt(s,t=15e3){const n=new AbortController,e=setTimeout(()=>n.abort(),t);try{const o=await fetch(s,{headers:{"User-Agent":at},signal:n.signal});if(!o.ok)throw new tt(o.status,`HTTP ${o.status} for ${s}`);return await o.text()}finally{clearTimeout(e)}}async function U(s,t=1e4){const n=new AbortController,e=setTimeout(()=>n.abort(),t);try{const o=await fetch(s,{headers:{"User-Agent":at},signal:n.signal});if(!o.ok)throw new tt(o.status,`HTTP ${o.status} for ${s}`);return await o.json()}finally{clearTimeout(e)}}async function D(s){const t=`dom:${s}`,n=q(t);if(n)return console.log(`[cache hit] ${s}`),yt(t)&&(console.log(`[stale refresh] ${s}`),K(()=>nt(s)).then(o=>M(t,o)).catch(()=>{})),Z(n);const e=await K(()=>nt(s));return M(t,e),Z(e)}function X(s){const t=Z(`<body>${s}</body>`);return t("img").remove(),t("summary.hideme").remove(),t("div.example-wrap").each((n,e)=>{const o=t(e).find("pre").text().trim();o&&t(e).replaceWith(`
3
+ \`\`\`rust
4
+ ${o}
5
+ \`\`\`
6
+ `)}),t("pre.rust").each((n,e)=>{const o=t(e).text().trim();o&&t(e).replaceWith(`
7
+ \`\`\`rust
8
+ ${o}
9
+ \`\`\`
10
+ `)}),t("pre").each((n,e)=>{const o=t(e).text().trim();o&&t(e).replaceWith(`
11
+ \`\`\`
12
+ ${o}
13
+ \`\`\`
14
+ `)}),t("code").each((n,e)=>{const o=t(e).text();o&&!o.includes(`
15
+ `)&&t(e).replaceWith(`\`${o}\``)}),t("h1").each((n,e)=>{t(e).replaceWith(`
16
+ # ${t(e).text().trim()}
17
+ `)}),t("h2").each((n,e)=>{t(e).replaceWith(`
18
+ ## ${t(e).text().trim()}
19
+ `)}),t("h3").each((n,e)=>{t(e).replaceWith(`
20
+ ### ${t(e).text().trim()}
21
+ `)}),t("h4,h5,h6").each((n,e)=>{t(e).replaceWith(`
22
+ #### ${t(e).text().trim()}
23
+ `)}),t("p, div, br, li, tr").each((n,e)=>{t(e).before(`
3
24
  `)}),t("body").text().replace(/\n{3,}/g,`
4
25
 
5
- `).trim()}function T(e,t){return e.length>t?e.slice(0,t)+`
26
+ `).trim()}function H(s,t){if(s.length<=t)return s;const n=s.slice(0,t),e=n.match(/```/g),o=e&&e.length%2!==0;return n+(o?"\n```":"")+`
6
27
 
7
- […truncated]`:e}function v(e){return{content:[{type:"text",text:e}]}}function k(e){return{content:[{type:"text",text:`Error: ${e}`}],isError:!0}}async function Q(e){const t=`crate-info:${e}`,o=F(t);if(o)return console.log(`[cache hit] crate-info ${e}`),o;const n=(await q(()=>U(`${K}/crates/${e}`))).crate,a={name:n.name,version:n.max_stable_version||n.max_version,description:n.description,documentation:n.documentation??null,repository:n.repository??null,downloads:n.downloads};return P(t,a),a}async function at(e,t){const o=`crate-version:${e}@${t}`,s=F(o);if(s)return console.log(`[cache hit] crate-version ${e}@${t}`),s;const a=(await q(()=>U(`${K}/crates/${e}/${t}`))).version,l=a.features??{},i={num:a.num,features:l,defaultFeatures:l.default??[],yanked:a.yanked,license:a.license,msrv:a.rust_version??null};return P(o,i),i}async function yt(e,t){const o=`crate-deps:${e}@${t}`,s=F(o);if(s)return console.log(`[cache hit] crate-deps ${e}@${t}`),s;const a=((await q(()=>U(`${K}/crates/${e}/${t}/dependencies`))).dependencies??[]).map(l=>({name:l.crate_id,req:l.req,optional:l.optional,kind:l.kind,features:l.features??[]}));return P(o,a),a}async function B(e,t,o="latest"){const s=j(e,"all.html",o),n=await I(s),a=t.toLowerCase(),l=[];return n("h3").each((i,c)=>{const d=n(c).attr("id")??"",p=H[d]??d;n(c).next("ul.all-items").find("li a").each((h,f)=>{const r=n(f).text().trim(),u=r.toLowerCase(),m=r.includes("::")?r.split("::").pop():r,y=m.toLowerCase();let w=0;if(y===a?w=100:u===a?w=95:y.startsWith(a)?w=60:u.startsWith(a)?w=55:u.includes(a)&&(w=20),w>0){const x=r.split("::"),b=x.length>1?x.slice(0,-1).join("."):"";l.push({type:p,name:r,path:r,modulePath:b,bareName:m,score:w})}})}),l.sort((i,c)=>c.score-i.score||i.name.localeCompare(c.name)),l}function Z(e){return e(".item-info .stab.portability").first().text().trim()||null}function bt(e){const t=[];return e("div.example-wrap pre.rust").each((o,s)=>{const n=e(s).text().trim();n&&t.push(n)}),t}function wt(e){const t=[];return e("#trait-implementations-list > details > summary h3.code-header").each((o,s)=>{t.push(e(s).text().trim())}),t}function it(e){const t=[];return e("h2#reexports").next("dl.item-table").find("dt code").each((o,s)=>{t.push(e(s).text().trim())}),t}const N=g.enum(["mod","struct","enum","trait","fn","macro","type","constant","static","union","attr","derive"]),A=g.string().optional().describe('Crate version (e.g. "1.49.0"). Defaults to latest.');function vt(e){e.tool("lookup_crate_docs","Fetch the main documentation for a Rust crate. Returns overview, version, sections, and re-exports.",{crateName:g.string().describe('Crate name (e.g. "tokio", "serde-json")'),version:A},async({crateName:t,version:o})=>{try{const s=o??"latest",n=j(t,"index.html",s),a=await I(n),l=a(".sidebar-crate .version").text().trim()||s,i=T(z(a("details.toggle.top-doc").html()??""),ot),c=[];a("h2.section-header").each((h,f)=>{const r=a(f).attr("id")??"",u=a(f).next("dl.item-table").find("dt").length;r&&u&&c.push(` ${a(f).text().trim()} (${u})`)});const d=it(a),p=[`# ${t} v${l}`,n,"",i];return d.length&&p.push("","## Re-exports",...d.map(h=>` ${h}`)),p.push("","## Sections",...c),v(p.join(`
8
- `))}catch(s){return k(`Could not fetch docs for "${t}". ${s.message}`)}})}function xt(e){e.tool("get_crate_items","List public items in a crate root or module. Returns names, types, feature gates, and short descriptions. Supports filtering by item type and feature gate.",{crateName:g.string().describe("Crate name"),modulePath:g.string().optional().describe('Dot-separated module path (e.g. "sync", "io.util"). Omit for crate root.'),itemType:N.optional().describe("Filter results to a single item type"),feature:g.string().optional().describe('Filter to items gated behind this feature (e.g. "sync", "fs")'),version:A},async({crateName:t,modulePath:o,itemType:s,feature:n,version:a})=>{try{const l=a??"latest",i=j(t,`${W(o)}index.html`,l),c=await I(i),d=[];c("h2.section-header").each((r,u)=>{const m=c(u).attr("id")??"",y=H[m]??m;s&&y!==s||c(u).next("dl.item-table").find("dt").each((w,x)=>{const b=c(x),S=b.find("a").first().text().trim(),_=b.next("dd").text().trim(),C=b.find(".stab.portability code").first().text().trim();if(!S||n&&(!C||!C.toLowerCase().includes(n.toLowerCase())))return;const $=C?` [feature: ${C}]`:"";d.push(`[${y}] ${S}${$} — ${_}`)})});const p=o?`${t}::${o.replace(/\./g,"::")}`:t,h=[];s&&h.push(`type: ${s}`),n&&h.push(`feature: ${n}`);const f=h.length?` (${h.join(", ")})`:"";return d.length?v([`# Items in ${p}${f}`,i,"",...d].join(`
9
- `)):v(`No items found in ${p}${f}.`)}catch(l){return k(`Could not list items. ${l.message}`)}})}function _t(e){e.tool("lookup_crate_item","Get detailed documentation for a specific item. Returns signature, docs, feature gate, methods, trait impls, and optionally examples. Auto-discovers modulePath if omitted.",{crateName:g.string().describe("Crate name"),itemType:N.describe("Item type"),itemName:g.string().describe('Item name (e.g. "Mutex", "spawn", "Serialize")'),modulePath:g.string().optional().describe('Dot-separated module path (e.g. "sync"). Auto-discovered if omitted.'),version:A,includeExamples:g.boolean().optional().describe("Include code examples from the docs. Default: false."),includeImpls:g.boolean().optional().describe("Include trait implementation list. Default: false.")},async({crateName:t,itemType:o,itemName:s,modulePath:n,version:a,includeExamples:l,includeImpls:i})=>{try{const c=a??"latest";let d=n;if(d===void 0){const $=await B(t,s,c),R=$.find(E=>E.bareName.toLowerCase()===s.toLowerCase()&&E.type===o)??$.find(E=>E.bareName.toLowerCase()===s.toLowerCase());R&&(d=R.modulePath||void 0,console.log(`[auto-discovery] ${s} → ${R.name} (${R.type})`))}const p=W(d),h=o==="mod"?`${p}${s}/index.html`:`${p}${Y[o]??`${o}.`}${s}.html`,f=j(t,h,c);let r;try{r=await I(f)}catch($){const E=(await B(t,s,c)).filter(M=>M.bareName.toLowerCase().includes(s.toLowerCase())||s.toLowerCase().includes(M.bareName.toLowerCase()));if(E.length){const M=E.slice(0,10).map(O=>` [${O.type}] ${t}::${O.name}`);return v([`Could not find ${o} "${s}" at the expected path.`,"","Did you mean one of these?",...M,"","Tip: use search_crate to find the exact name and module path."].join(`
10
- `))}throw $}const u=`${t}::${rt(d)}${s}`,m=r("pre.rust.item-decl").text().trim(),y=Z(r),w=T(z(r("details.toggle.top-doc").html()??""),ot),x=[];r("#implementations-list section.method h4.code-header").each(($,R)=>{x.push(r(R).text().trim())});const b=[];r("h2#required-methods").first().nextUntil("h2").find("section h4.code-header").each(($,R)=>{b.push(r(R).text().trim())});const S=[];r("h2#provided-methods").first().nextUntil("h2").find("section h4.code-header").each(($,R)=>{S.push(r(R).text().trim())});const _=[];r("section.variant h3.code-header, div.variant h3.code-header").each(($,R)=>{_.push(r(R).text().trim())});const C=[`# ${o} ${u}`,f,""];if(y&&C.push(`> ${y}`,""),m&&C.push("## Signature","```rust",m,"```",""),w&&C.push("## Documentation",w,""),_.length&&C.push(`## Variants (${_.length})`,..._.map($=>` ${$}`),""),b.length&&C.push(`## Required Methods (${b.length})`,...b.map($=>` ${$}`),""),S.length&&C.push(`## Provided Methods (${S.length})`,...S.map($=>` ${$}`),""),x.length&&C.push(`## Methods (${x.length})`,...x.map($=>` ${$}`),""),i){const $=wt(r);$.length&&C.push(`## Trait Implementations (${$.length})`,...$.map(R=>` ${R}`),"")}if(l){const $=bt(r);$.length&&(C.push(`## Examples (${$.length})`),$.forEach((R,E)=>{C.push(`### Example ${E+1}`,"```rust",R,"```","")}))}return v(C.join(`
11
- `))}catch(c){return k(`Could not fetch ${o} "${s}". ${c.message}`)}})}function Ct(e,t){const o=e.toLowerCase(),s=t.toLowerCase(),n=o.includes("::")?o.split("::").pop():o;return n===s?100:o===s?95:n.startsWith(s)?60:o.startsWith(s)?55:o.includes(s)?20:0}function Rt(e){e.tool("search_crate","Search for items by name within a Rust crate. Returns ranked results with canonical paths and item types.",{crateName:g.string().describe("Crate name"),query:g.string().describe("Search query (matched against item names)"),version:A},async({crateName:t,query:o,version:s})=>{try{const a=j(t,"all.html",s??"latest"),l=await I(a),i=[];if(l("h3").each((h,f)=>{const r=l(f).attr("id")??"",u=H[r]??r;l(f).next("ul.all-items").find("li a").each((m,y)=>{const w=l(y).text().trim(),x=l(y).attr("href")??"",b=Ct(w,o);b>0&&i.push({type:u,name:w,href:x,score:b})})}),!i.length)return v(`No matches for "${o}" in ${t}.`);i.sort((h,f)=>f.score-h.score||h.name.localeCompare(f.name));const d=i.slice(0,V).map(h=>{const f=`${t}::${h.name.replace(/::/g,"::")}`;return`[${h.type}] ${f}`}),p=i.length>V?` (showing first ${V})`:"";return v([`# "${o}" in ${t} — ${i.length} matches${p}`,"",...d].join(`
12
- `))}catch(n){return k(`Could not search "${t}". ${n.message}`)}})}function St(e){e.tool("get_crate_metadata","Get crate metadata from crates.io: version, features, default features, optional dependencies, and links.",{crateName:g.string().describe("Crate name"),version:A},async({crateName:t,version:o})=>{try{if(G(t))return v(`"${t}" is part of the Rust standard library and is not published on crates.io.
13
- Use lookup_crate_docs, get_crate_items, lookup_crate_item, or search_crate to browse its documentation.`);const s=await Q(t),n=o??s.version,[a,l]=await Promise.all([at(t,n),yt(t,n)]),i=[`# ${s.name} v${a.num}`,"",`${s.description}`,"","## Links"];s.documentation&&i.push(` docs: ${s.documentation}`),s.repository&&i.push(` repo: ${s.repository}`),i.push(` crates.io: https://crates.io/crates/${s.name}`),i.push(` license: ${a.license}`),i.push(` downloads: ${s.downloads.toLocaleString()}`),a.msrv&&i.push(` msrv: ${a.msrv}`);const{features:c,defaultFeatures:d}=a;i.push("","## Features"),i.push(` default = [${d.join(", ")}]`);const p=Object.keys(c).filter(r=>r!=="default").sort();for(const r of p){const u=c[r],m=u.length?` = [${u.join(", ")}]`:"";i.push(` ${r}${m}`)}const h=l.filter(r=>r.optional&&r.kind==="normal");if(h.length){i.push("","## Optional Dependencies");for(const r of h)i.push(` ${r.name} ${r.req} (feature-gated)`)}const f=l.filter(r=>!r.optional&&r.kind==="normal");if(f.length){i.push("","## Required Dependencies");for(const r of f)i.push(` ${r.name} ${r.req}`)}return a.yanked&&i.push("","> WARNING: This version has been yanked."),v(i.join(`
14
- `))}catch(s){return k(`Could not fetch metadata for "${t}". ${s.message}`)}})}function kt(e){e.tool("get_crate_brief","Bundle call: fetches crate metadata, overview docs, module list, re-exports, and optionally items from focused modules — all in one shot.",{crateName:g.string().describe("Crate name"),version:A,focusModules:g.string().optional().describe('Comma-separated module names to expand (e.g. "sync,task,io"). Omit for overview only.')},async({crateName:t,version:o,focusModules:s})=>{try{const n=G(t);let a=null,l=null;if(!n){const u=await Q(t),m=o??u.version;l=await at(t,m),a=u}const i=n?"latest":o??a.version,c=j(t,"index.html",i),d=await I(c),p=T(z(d("details.toggle.top-doc").html()??""),3e3),h=it(d),f={};d("h2.section-header").each((u,m)=>{const y=d(m).attr("id")??"",w=H[y]??y,x=[];d(m).next("dl.item-table").find("dt").each((b,S)=>{const _=d(S),C=_.find("a").first().text().trim(),$=_.find(".stab.portability code").first().text().trim();C&&x.push($?`${C} [${$}]`:C)}),x.length&&(f[w]=x)});const r=[];if(n){const u=d(".sidebar-crate .version").text().trim()||"stable";r.push(`# ${t} (Rust standard library) v${u}`,c,"",`The "${t}" crate is part of the Rust standard library.`)}else{r.push(`# ${a.name} v${l.num}`,c,"",a.description,"",`license: ${l.license} | downloads: ${a.downloads.toLocaleString()}`),a.repository&&r.push(`repo: ${a.repository}`);const{defaultFeatures:u,features:m}=l;r.push("","## Features",` default = [${u.join(", ")}]`,` all: ${Object.keys(m).filter(y=>y!=="default").sort().join(", ")}`)}p&&r.push("","## Overview",p),h.length&&r.push("","## Re-exports",...h.map(u=>` ${u}`)),f.mod&&r.push("","## Modules",...f.mod.map(u=>` ${u}`));for(const[u,m]of Object.entries(f))u==="mod"||u==="reexport"||r.push("",`## ${u} (${m.length})`,...m.map(y=>` ${y}`));if(s){const u=s.split(",").map(m=>m.trim()).filter(Boolean);for(const m of u)try{const y=j(t,`${W(m)}index.html`,i),w=await I(y);r.push("",`## Focus: ${t}::${m.replace(/\./g,"::")}`,y),w("h2.section-header").each((x,b)=>{const S=w(b).attr("id")??"",_=H[S]??S;w(b).next("dl.item-table").find("dt").each((C,$)=>{const R=w($),E=R.find("a").first().text().trim(),M=R.next("dd").text().trim(),O=R.find(".stab.portability code").first().text().trim();if(!E)return;const ct=O?` [${O}]`:"";r.push(` [${_}] ${E}${ct} — ${M}`)})})}catch{r.push("",`## Focus: ${m}`," (module not found)")}}return v(r.join(`
15
- `))}catch(n){return k(`Could not fetch brief for "${t}". ${n.message}`)}})}function Lt(e){e.tool("search_crates","Search for Rust crates on crates.io by keyword. Returns name, description, downloads, and version.",{query:g.string().describe("Search keywords"),page:g.number().min(1).optional().describe("Page number (default 1)"),perPage:g.number().min(1).max(50).optional().describe("Results per page (default 10, max 50)")},async({query:t,page:o,perPage:s})=>{const n=o??1,a=s??10;try{const l=`search-crates:${t}:${n}:${a}`,i=F(l);if(i)return console.log(`[cache hit] search-crates "${t}" page=${n}`),v(i);const c=new URLSearchParams({q:t,per_page:String(a),page:String(n)}),d=await U(`${K}/crates?${c}`),p=d.crates??[];if(!p.length)return v(`No crates found for "${t}".`);const h=d.meta?.total??p.length,f=p.map(u=>{const m=u.max_stable_version||u.max_version,y=u.downloads.toLocaleString(),w=u.description?` — ${u.description.trim()}`:"";return` ${u.name} v${m} (${y} downloads)${w}`}),r=[`# Crate search: "${t}" — ${h} results (page ${n})`,"",...f].join(`
16
- `);return P(l,r),v(r)}catch(l){return k(`Could not search crates.io for "${t}". ${l.message}`)}})}function Et(e){e.tool("get_crate_versions","List all published versions of a crate from crates.io, with yanked status and release dates.",{crateName:g.string().describe("Crate name")},async({crateName:t})=>{try{if(G(t))return v(`"${t}" is part of the Rust standard library and is not published on crates.io.
17
- Its version matches the Rust toolchain version.`);const o=`crate-versions:${t}`,s=F(o);if(s)return console.log(`[cache hit] crate-versions ${t}`),v(s);const a=((await U(`${K}/crates/${t}/versions`)).versions??[]).map(c=>({num:c.num,yanked:c.yanked,created_at:c.created_at,license:c.license??""}));if(!a.length)return v(`No versions found for "${t}".`);const l=a.map(c=>{const d=c.created_at.slice(0,10),p=c.yanked?" [YANKED]":"";return` ${c.num} ${d}${p}`}),i=[`# ${t} — ${a.length} versions`,"",...l].join(`
18
- `);return P(o,i),v(i)}catch(o){return k(`Could not fetch versions for "${t}". ${o.message}`)}})}function It(e){e.tool("get_source_code","Fetch the source code of a Rust item from docs.rs. Returns the raw source implementation.",{crateName:g.string().describe("Crate name"),path:g.string().describe('Source path relative to crate root (e.g. "src/lib.rs", "src/sync/mutex.rs")'),version:A},async({crateName:t,path:o,version:s})=>{try{const n=o.replace(/^src\//,"");if(G(t)){const d=`https://doc.rust-lang.org/stable/src/${t}/${n}.html`,p=await I(d),h=p("#source-code").text().trim()||p("pre.rust").text().trim();return h?v([`# Source: ${t}/${o}`,d,"","```rust",T(h,12e3),"```"].join(`
19
- `)):k(`No source code found at ${d}`)}const l=`${et}/${t}/${s??"latest"}/src/${nt(t)}/${n}.html`,i=await I(l),c=i("#source-code").text().trim()||i("pre.rust").text().trim()||i(".src-line-numbers + code").text().trim();return c?v([`# Source: ${t}/${o}`,l,"","```rust",T(c,12e3),"```"].join(`
20
- `)):k(`No source code found at ${l}. Check that the path is correct.`)}catch(n){return k(`Could not fetch source for "${t}/${o}". ${n.message}`)}})}const jt=g.object({itemType:N.describe("Item type"),itemName:g.string().describe("Item name"),modulePath:g.string().optional().describe("Dot-separated module path")});function At(e){e.tool("batch_lookup","Look up multiple items in a single call. Returns a compact summary (signature + short doc) for each item. Saves round-trips when you need several items.",{crateName:g.string().describe("Crate name"),items:g.array(jt).min(1).max(20).describe("Items to look up (max 20)"),version:A},async({crateName:t,items:o,version:s})=>{const n=s??"latest",a=[`# Batch lookup: ${t} (${o.length} items)`,""],l=await Promise.allSettled(o.map(async({itemType:i,itemName:c,modulePath:d})=>{let p=d;if(p===void 0){const b=await B(t,c,n),S=b.find(_=>_.bareName.toLowerCase()===c.toLowerCase()&&_.type===i)??b.find(_=>_.bareName.toLowerCase()===c.toLowerCase());S&&(p=S.modulePath||void 0)}const h=W(p),f=i==="mod"?`${h}${c}/index.html`:`${h}${Y[i]??`${i}.`}${c}.html`,r=j(t,f,n),u=await I(r),m=`${t}::${rt(p)}${c}`,y=u("pre.rust.item-decl").text().trim(),w=Z(u),x=T(z(u("details.toggle.top-doc").html()??""),500);return{itemType:i,fullName:m,decl:y,featureGate:w,doc:x,url:r}}));for(let i=0;i<l.length;i++){const c=l[i],{itemType:d,itemName:p}=o[i];if(c.status==="fulfilled"){const{fullName:h,decl:f,featureGate:r,doc:u,url:m}=c.value;a.push(`## ${d} ${h}`,m),r&&a.push(`> ${r}`),f&&a.push("```rust",f,"```"),u&&a.push(u),a.push("")}else a.push(`## ${d} ${p}`,` Error: ${c.reason?.message??"unknown error"}`,"")}return v(a.join(`
21
- `))})}function Pt(e){const t=e.match(/github\.com\/([^/]+\/[^/]+)/);return t?t[1].replace(/\.git$/,""):null}function Tt(e){e.tool("get_crate_changelog","Fetch recent GitHub releases for a crate. Requires the crate to have a GitHub repository link on crates.io.",{crateName:g.string().describe("Crate name"),count:g.number().min(1).max(20).optional().describe("Number of releases to fetch (default 5, max 20)")},async({crateName:t,count:o})=>{const s=o??5;try{if(G(t))return v(`"${t}" is part of the Rust standard library.
22
- See https://github.com/rust-lang/rust/blob/master/RELEASES.md for changelogs.`);const n=`changelog:${t}:${s}`,a=F(n);if(a)return console.log(`[cache hit] changelog ${t}`),v(a);const l=await Q(t);if(!l.repository)return k(`No repository link found for "${t}" on crates.io.`);const i=Pt(l.repository);if(!i)return k(`Repository "${l.repository}" is not a GitHub URL.`);const c=await U(`https://api.github.com/repos/${i}/releases?per_page=${s}`);if(!c.length)return v(`No GitHub releases found for ${i}.`);const d=[`# ${t} recent releases (${i})`,""];for(const h of c){const f=h.published_at?.slice(0,10)??"unknown",r=h.name||h.tag_name,u=h.body?T(h.body.trim(),1e3):"(no release notes)";d.push(`## ${r} (${f})`,u,"")}const p=d.join(`
23
- `);return P(n,p),v(p)}catch(n){return k(`Could not fetch changelog for "${t}". ${n.message}`)}})}function Dt(e){e.tool("resolve_type",'Resolve a Rust type path (e.g. "tokio::sync::Mutex" or "std::collections::HashMap") to its documentation. Parses the path to determine the crate, module, and item name automatically.',{typePath:g.string().describe('Full Rust type path (e.g. "tokio::sync::Mutex", "std::collections::HashMap")'),version:A},async({typePath:t,version:o})=>{try{const s=t.split("::").filter(Boolean);if(s.length<2)return k(`Type path "${t}" must have at least a crate and item name (e.g. "serde::Serialize").`);const n=s[0],a=s[s.length-1],l=o??"latest",i=await B(n,a,l),c=i.find(b=>{const S=b.name.replace(/::/g,"::"),_=s.slice(1).join("::");return S===_&&b.bareName===a})??i.find(b=>b.bareName===a);if(!c){const b=i.slice(0,10).map(_=>` [${_.type}] ${n}::${_.name}`),S=[`Could not resolve "${t}".`];return b.length&&S.push("","Similar items found:",...b),v(S.join(`
24
- `))}const d=c.modulePath?c.modulePath.replace(/\./g,"/")+"/":"",p=Y[c.type]??`${c.type}.`,h=c.type==="mod"?`${d}${a}/index.html`:`${d}${p}${a}.html`,f=j(n,h,l),r=await I(f),u=r("pre.rust.item-decl").text().trim(),m=Z(r),y=T(z(r("details.toggle.top-doc").html()??""),2e3),w=`${n}::${c.name}`,x=[`# ${c.type} ${w}`,f,""];return m&&x.push(`> ${m}`,""),u&&x.push("```rust",u,"```",""),y&&x.push(y),v(x.join(`
25
- `))}catch(s){return k(`Could not resolve "${t}". ${s.message}`)}})}console.log=(...e)=>console.error(...e);const L=new lt({name:"rust-docs",version:"4.0.0"});vt(L);xt(L);_t(L);Rt(L);St(L);kt(L);Lt(L);Et(L);It(L);At(L);Tt(L);Dt(L);L.prompt("lookup_crate_docs",{crateName:g.string().describe("Crate name")},({crateName:e})=>({messages:[{role:"user",content:{type:"text",text:[`Analyze the documentation for the Rust crate '${e}'. Focus on:`,"1. Main purpose and features","2. Key types and functions","3. Common usage patterns","4. Important notes or warnings","5. Latest version"].join(`
26
- `)}}]}));async function Ft(){const e=new ut;await L.connect(e)}Ft().catch(e=>{console.error("Failed to start server:",e),process.exit(1)});
28
+ […truncated]`}function b(s){return{content:[{type:"text",text:s}]}}function L(s){return{content:[{type:"text",text:`Error: ${s}`}],isError:!0}}async function et(s){const t=`crate-info:${s}`,n=q(t);if(n)return console.log(`[cache hit] crate-info ${s}`),n;const o=(await K(()=>U(`${B}/crates/${s}`))).crate,r={name:o.name,version:o.max_stable_version||o.max_version,description:o.description,documentation:o.documentation??null,repository:o.repository??null,downloads:o.downloads};return M(t,r),r}async function lt(s,t){const n=`crate-version:${s}@${t}`,e=q(n);if(e)return console.log(`[cache hit] crate-version ${s}@${t}`),e;const r=(await K(()=>U(`${B}/crates/${s}/${t}`))).version,l=r.features??{},i={num:r.num,features:l,defaultFeatures:l.default??[],yanked:r.yanked,license:r.license,msrv:r.rust_version??null};return M(n,i),i}async function vt(s,t){const n=`crate-deps:${s}@${t}`,e=q(n);if(e)return console.log(`[cache hit] crate-deps ${s}@${t}`),e;const r=((await K(()=>U(`${B}/crates/${s}/${t}/dependencies`))).dependencies??[]).map(l=>({name:l.crate_id,req:l.req,optional:l.optional,kind:l.kind,features:l.features??[]}));return M(n,r),r}async function z(s,t,n="latest"){const e=j(s,"all.html",n),o=await D(e),r=t.toLowerCase(),l=[];return o("h3").each((i,c)=>{const h=o(c).attr("id")??"",p=W[h]??h;o(c).next("ul.all-items").find("li a").each((m,f)=>{const a=o(f).text().trim(),u=a.toLowerCase(),d=a.includes("::")?a.split("::").pop():a,$=d.toLowerCase();let g=0;if($===r?g=100:u===r?g=95:$.startsWith(r)?g=60:u.startsWith(r)?g=55:u.includes(r)&&(g=20),g>0){const v=a.split("::"),k=v.length>1?v.slice(0,-1).join("."):"";l.push({type:p,name:a,path:a,modulePath:k,bareName:d,score:g})}})}),l.sort((i,c)=>c.score-i.score||i.name.localeCompare(c.name)),l}function st(s){return s(".item-info .stab.portability").first().text().trim()||null}function _t(s){const t=[];return s("div.example-wrap pre.rust").each((n,e)=>{const o=s(e).text().trim();o&&t.push(o)}),t}function Ct(s){const t=[];return s("#trait-implementations-list > details > summary h3.code-header").each((n,e)=>{t.push(s(e).text().trim())}),t}function ut(s){const t=[];return s("h2#reexports").next("dl.item-table").find("dt code").each((n,e)=>{t.push(s(e).text().trim())}),t}function kt(s){const t=[],n=new Set;return s('section[id^="method."], section[id^="tymethod."]').each((e,o)=>{const r=s(o),l=r.attr("id")??"";if(n.has(l))return;n.add(l);const i=l.replace(/^(ty)?method\./,""),c=r.find("h4.code-header").first().text().trim(),h=r.find(".stab.deprecated").length>0||r.parent("details").find(".stab.deprecated").length>0,p=r.find(".stab.portability code").first().text().trim()||null,m=r.parent("details").find(".docblock p").first().text().trim()||r.next(".docblock").find("p").first().text().trim();i&&c&&t.push({name:i,signature:c,deprecated:h,featureGate:p,shortDoc:m})}),t}function dt(s){return s(".item-info .stab.deprecated").first().text().trim()||null}function ht(s){return s(".item-info .stab.unstable").first().text().trim()||null}function Rt(s,t){const n=s.length,e=t.length;if(!n)return e;if(!e)return n;const o=Array.from({length:e+1},(r,l)=>l);for(let r=1;r<=n;r++){let l=r-1;o[0]=r;for(let i=1;i<=e;i++){const c=o[i];o[i]=s[r-1]===t[i-1]?l:1+Math.min(l,o[i],o[i-1]),l=c}}return o[e]}const Q=y.enum(["mod","struct","enum","trait","fn","macro","type","constant","static","union","attr","derive"]),A=y.string().optional().describe('Crate version (e.g. "1.49.0"). Defaults to latest.');function St(s){s.tool("lookup_crate_docs","Fetch the main documentation for a Rust crate. Returns overview, version, sections, and re-exports.",{crateName:y.string().describe('Crate name (e.g. "tokio", "serde-json")'),version:A},{readOnlyHint:!0},async({crateName:t,version:n})=>{try{const e=n??"latest",o=j(t,"index.html",e),r=await D(o),l=r(".sidebar-crate .version").text().trim()||e,i=H(X(r("details.toggle.top-doc").html()??""),it),c=[];r("h2.section-header").each((m,f)=>{const a=r(f).attr("id")??"",u=r(f).next("dl.item-table").find("dt").length;a&&u&&c.push(` ${r(f).text().trim()} (${u})`)});const h=ut(r),p=[`# ${t} v${l}`,o,"",i];return h.length&&p.push("","## Re-exports",...h.map(m=>` ${m}`)),p.push("","## Sections",...c),b(p.join(`
29
+ `))}catch(e){return L(`Could not fetch docs for "${t}". ${e.message}
30
+ Tip: check the crate name with search_crates({ query: "${t}" }).`)}})}function Lt(s){s.tool("get_crate_items","List public items in a crate root or module. Returns names, types, feature gates, and short descriptions. Supports filtering by item type and feature gate.",{crateName:y.string().describe("Crate name"),modulePath:y.string().optional().describe('Dot-separated module path (e.g. "sync", "io.util"). Omit for crate root.'),itemType:Q.optional().describe("Filter results to a single item type"),feature:y.string().optional().describe('Filter to items gated behind this feature (e.g. "sync", "fs")'),version:A},{readOnlyHint:!0},async({crateName:t,modulePath:n,itemType:e,feature:o,version:r})=>{try{const l=r??"latest",i=j(t,`${V(n)}index.html`,l),c=await D(i),h=[];c("h2.section-header").each((d,$)=>{const g=c($).attr("id")??"",v=W[g]??g;e&&v!==e||c($).next("dl.item-table").find("dt").each((k,_)=>{const x=c(_),R=x.find("a").first().text().trim(),E=x.next("dd").text().trim(),I=x.find(".stab.portability code").first().text().trim();if(!R||o&&(!I||!I.toLowerCase().includes(o.toLowerCase())))return;const S=I?` [feature: ${I}]`:"";h.push(`[${v}] ${R}${S} — ${E}`)})});const p=n?`${t}::${n.replace(/\./g,"::")}`:t,m=[];e&&m.push(`type: ${e}`),o&&m.push(`feature: ${o}`);const f=m.length?` (${m.join(", ")})`:"";if(!h.length)return b(`No items found in ${p}${f}.`);const a=h.slice(0,G),u=h.length>G?`
31
+
32
+ […${h.length-G} more items. Narrow with itemType or feature filter.]`:"";return b([`# Items in ${p}${f} (${h.length})`,i,"",...a].join(`
33
+ `)+u)}catch(l){return L(`Could not list items for "${t}". ${l.message}
34
+ Tip: check the module path exists with lookup_crate_docs({ crateName: "${t}" }).`)}})}function Et(s){s.tool("lookup_crate_item","Get detailed documentation for a specific item. Returns signature, docs, feature gate, methods, trait impls, and optionally examples. Auto-discovers modulePath if omitted.",{crateName:y.string().describe("Crate name"),itemType:Q.describe("Item type"),itemName:y.string().describe('Item name (e.g. "Mutex", "spawn", "Serialize")'),modulePath:y.string().optional().describe('Dot-separated module path (e.g. "sync"). Auto-discovered if omitted.'),version:A,includeExamples:y.boolean().optional().describe("Include code examples from the docs. Default: false."),includeImpls:y.boolean().optional().describe("Include trait implementation list. Default: false.")},{readOnlyHint:!0},async({crateName:t,itemType:n,itemName:e,modulePath:o,version:r,includeExamples:l,includeImpls:i})=>{try{const c=r??"latest";let h=o;if(h===void 0){const w=await z(t,e,c),C=w.find(P=>P.bareName.toLowerCase()===e.toLowerCase()&&P.type===n)??w.find(P=>P.bareName.toLowerCase()===e.toLowerCase());C&&(h=C.modulePath||void 0,console.log(`[auto-discovery] ${e} ${C.name} (${C.type})`))}const p=V(h),m=n==="mod"?`${p}${e}/index.html`:`${p}${J[n]??`${n}.`}${e}.html`,f=j(t,m,c);let a;try{a=await D(f)}catch(w){const P=(await z(t,e,c)).filter(Y=>Y.bareName.toLowerCase().includes(e.toLowerCase())||e.toLowerCase().includes(Y.bareName.toLowerCase()));if(P.length){const Y=P.slice(0,10).map(ot=>` [${ot.type}] ${t}::${ot.name}`);return b([`Could not find ${n} "${e}" at the expected path.`,"","Did you mean one of these?",...Y,"","Tip: use search_crate to find the exact name and module path."].join(`
35
+ `))}throw w}const u=`${t}::${N(h)}${e}`,d=a("pre.rust.item-decl").text().trim(),$=st(a),g=dt(a),v=ht(a),k=it,_=H(X(a("details.toggle.top-doc").html()??""),k),x=[];a("#implementations-list section.method h4.code-header").each((w,C)=>{x.push(a(C).text().trim())});const R=[];a("h2#required-methods").first().nextUntil("h2").find("section h4.code-header").each((w,C)=>{R.push(a(C).text().trim())});const E=[];a("h2#provided-methods").first().nextUntil("h2").find("section h4.code-header").each((w,C)=>{E.push(a(C).text().trim())});const I=[];a("section.variant h3.code-header, div.variant h3.code-header").each((w,C)=>{I.push(a(C).text().trim())});const S=[`# ${n} ${u}`,f,""];if(g&&S.push(`> DEPRECATED: ${g}`,""),v&&S.push(`> ${v}`,""),$&&S.push(`> ${$}`,""),d&&S.push("## Signature","```rust",d,"```",""),_&&S.push("## Documentation",_,""),I.length&&S.push(`## Variants (${I.length})`,...I.map(w=>` ${w}`),""),R.length&&S.push(`## Required Methods (${R.length})`,...R.map(w=>` ${w}`),""),E.length&&S.push(`## Provided Methods (${E.length})`,...E.map(w=>` ${w}`),""),x.length&&S.push(`## Methods (${x.length})`,...x.map(w=>` ${w}`),"","Tip: use list_methods for full method details including deprecation and docs."),i){const w=Ct(a);w.length&&S.push(`## Trait Implementations (${w.length})`,...w.map(C=>` ${C}`),"")}if(l){const w=_t(a);w.length&&(S.push(`## Examples (${w.length})`),w.forEach((C,P)=>{S.push(`### Example ${P+1}`,"```rust",C,"```","")}))}return b(S.join(`
36
+ `))}catch(c){return L(`Could not fetch ${n} "${e}". ${c.message}
37
+ Tip: verify the item with search_crate({ crateName: "${t}", query: "${e}" }).`)}})}function Tt(s,t){const n=s.toLowerCase(),e=t.toLowerCase(),o=n.includes("::")?n.split("::").pop():n;return o===e?100:n===e?95:o.startsWith(e)?60:n.startsWith(e)?55:n.includes(e)?20:0}function It(s){s.tool("search_crate","Search for items by name within a Rust crate. Returns ranked results with canonical paths and item types.",{crateName:y.string().describe("Crate name"),query:y.string().describe("Search query (matched against item names)"),version:A},{readOnlyHint:!0},async({crateName:t,query:n,version:e})=>{try{const r=j(t,"all.html",e??"latest"),l=await D(r),i=[],c=[];if(l("h3").each((f,a)=>{const u=l(a).attr("id")??"",d=W[u]??u;l(a).next("ul.all-items").find("li a").each(($,g)=>{const v=l(g).text().trim(),k=l(g).attr("href")??"";i.push({type:d,name:v,href:k});const _=Tt(v,n);_>0&&c.push({type:d,name:v,href:k,score:_})})}),!c.length&&i.length){const f=n.toLowerCase(),a=[];for(const u of i){const d=u.name.includes("::")?u.name.split("::").pop().toLowerCase():u.name.toLowerCase(),$=Rt(d,f),g=Math.max(d.length,f.length);$<=Math.ceil(g*.4)&&a.push({...u,score:10,dist:$})}a.sort((u,d)=>u.dist-d.dist),c.push(...a.slice(0,20))}if(!c.length)return b(`No matches for "${n}" in ${t}.
38
+ Tip: try a shorter or broader query, or use search_crates to verify the crate name.`);c.sort((f,a)=>a.score-f.score||f.name.localeCompare(a.name));const p=c.slice(0,G).map(f=>{const a=`${t}::${f.name.replace(/::/g,"::")}`;return`[${f.type}] ${a}`}),m=c.length>G?`
39
+
40
+ […${c.length-G} more results. Use a more specific query.]`:"";return b([`# "${n}" in ${t} — ${c.length} matches`,"",...p].join(`
41
+ `)+m)}catch(o){return L(`Could not search "${t}". ${o.message}
42
+ Tip: check that "${t}" exists with search_crates({ query: "${t}" }).`)}})}function Dt(s){s.tool("get_crate_metadata","Get crate metadata from crates.io: version, features, default features, optional dependencies, and links.",{crateName:y.string().describe("Crate name"),version:A},{readOnlyHint:!0},async({crateName:t,version:n})=>{try{if(F(t))return b(`"${t}" is part of the Rust standard library and is not published on crates.io.
43
+ Use lookup_crate_docs, get_crate_items, lookup_crate_item, or search_crate to browse its documentation.`);const e=await et(t),o=n??e.version,[r,l]=await Promise.all([lt(t,o),vt(t,o)]),i=[`# ${e.name} v${r.num}`,"",`${e.description}`,"","## Links"];e.documentation&&i.push(` docs: ${e.documentation}`),e.repository&&i.push(` repo: ${e.repository}`),i.push(` crates.io: https://crates.io/crates/${e.name}`),i.push(` license: ${r.license}`),i.push(` downloads: ${e.downloads.toLocaleString()}`),r.msrv&&i.push(` msrv: ${r.msrv}`);const{features:c,defaultFeatures:h}=r;i.push("","## Features"),i.push(` default = [${h.join(", ")}]`);const p=Object.keys(c).filter(a=>a!=="default").sort();for(const a of p){const u=c[a],d=u.length?` = [${u.join(", ")}]`:"";i.push(` ${a}${d}`)}const m=l.filter(a=>a.optional&&a.kind==="normal");if(m.length){i.push("","## Optional Dependencies");for(const a of m)i.push(` ${a.name} ${a.req} (feature-gated)`)}const f=l.filter(a=>!a.optional&&a.kind==="normal");if(f.length){i.push("","## Required Dependencies");for(const a of f)i.push(` ${a.name} ${a.req}`)}return r.yanked&&i.push("","> WARNING: This version has been yanked."),b(i.join(`
44
+ `))}catch(e){return L(`Could not fetch metadata for "${t}". ${e.message}
45
+ Tip: verify the crate exists with search_crates({ query: "${t}" }).`)}})}function Pt(s){s.tool("get_crate_brief","Bundle call: fetches crate metadata, overview docs, module list, re-exports, and optionally items from focused modules — all in one shot.",{crateName:y.string().describe("Crate name"),version:A,focusModules:y.string().optional().describe('Comma-separated module names to expand (e.g. "sync,task,io"). Omit for overview only.')},{readOnlyHint:!0},async({crateName:t,version:n,focusModules:e})=>{try{const o=F(t);let r=null,l=null;if(!o){const u=await et(t),d=n??u.version;l=await lt(t,d),r=u}const i=o?"latest":n??r.version,c=j(t,"index.html",i),h=await D(c),p=H(X(h("details.toggle.top-doc").html()??""),3e3),m=ut(h),f={};h("h2.section-header").each((u,d)=>{const $=h(d).attr("id")??"",g=W[$]??$,v=[];h(d).next("dl.item-table").find("dt").each((k,_)=>{const x=h(_),R=x.find("a").first().text().trim(),E=x.find(".stab.portability code").first().text().trim();R&&v.push(E?`${R} [${E}]`:R)}),v.length&&(f[g]=v)});const a=[];if(o){const u=h(".sidebar-crate .version").text().trim()||"stable";a.push(`# ${t} (Rust standard library) v${u}`,c,"",`The "${t}" crate is part of the Rust standard library.`)}else{a.push(`# ${r.name} v${l.num}`,c,"",r.description,"",`license: ${l.license} | downloads: ${r.downloads.toLocaleString()}`),r.repository&&a.push(`repo: ${r.repository}`);const{defaultFeatures:u,features:d}=l;a.push("","## Features",` default = [${u.join(", ")}]`,` all: ${Object.keys(d).filter($=>$!=="default").sort().join(", ")}`)}p&&a.push("","## Overview",p),m.length&&a.push("","## Re-exports",...m.map(u=>` ${u}`)),f.mod&&a.push("","## Modules",...f.mod.map(u=>` ${u}`));for(const[u,d]of Object.entries(f))u==="mod"||u==="reexport"||a.push("",`## ${u} (${d.length})`,...d.map($=>` ${$}`));if(e){const u=e.split(",").map(d=>d.trim()).filter(Boolean);for(const d of u)try{const $=j(t,`${V(d)}index.html`,i),g=await D($);a.push("",`## Focus: ${t}::${d.replace(/\./g,"::")}`,$),g("h2.section-header").each((v,k)=>{const _=g(k).attr("id")??"",x=W[_]??_;g(k).next("dl.item-table").find("dt").each((R,E)=>{const I=g(E),S=I.find("a").first().text().trim(),w=I.next("dd").text().trim(),C=I.find(".stab.portability code").first().text().trim();if(!S)return;const P=C?` [${C}]`:"";a.push(` [${x}] ${S}${P} — ${w}`)})})}catch{a.push("",`## Focus: ${d}`," (module not found)")}}return b(a.join(`
46
+ `))}catch(o){return L(`Could not fetch brief for "${t}". ${o.message}
47
+ Tip: check the crate name with search_crates({ query: "${t}" }).`)}})}function jt(s){s.tool("search_crates","Search for Rust crates on crates.io by keyword. Returns name, description, downloads, and version.",{query:y.string().describe("Search keywords"),page:y.number().min(1).optional().describe("Page number (default 1)"),perPage:y.number().min(1).max(50).optional().describe("Results per page (default 10, max 50)")},{readOnlyHint:!0},async({query:t,page:n,perPage:e})=>{const o=n??1,r=e??10;try{const l=`search-crates:${t}:${o}:${r}`,i=q(l);if(i)return console.log(`[cache hit] search-crates "${t}" page=${o}`),b(i);const c=new URLSearchParams({q:t,per_page:String(r),page:String(o)}),h=await U(`${B}/crates?${c}`),p=h.crates??[];if(!p.length)return b(`No crates found for "${t}".`);const m=h.meta?.total??p.length,f=p.map($=>{const g=$.max_stable_version||$.max_version,v=$.downloads.toLocaleString(),k=$.description?` — ${$.description.trim()}`:"";return` ${$.name} v${g} (${v} downloads)${k}`}),u=o*r<m?`
48
+
49
+ [Page ${o} of ${Math.ceil(m/r)}. Use page: ${o+1} for more.]`:"",d=[`# Crate search: "${t}" — ${m} results (page ${o})`,"",...f].join(`
50
+ `)+u;return M(l,d),b(d)}catch(l){return L(`Could not search crates.io for "${t}". ${l.message}`)}})}function At(s){s.tool("get_crate_versions","List all published versions of a crate from crates.io, with yanked status and release dates.",{crateName:y.string().describe("Crate name")},{readOnlyHint:!0},async({crateName:t})=>{try{if(F(t))return b(`"${t}" is part of the Rust standard library and is not published on crates.io.
51
+ Its version matches the Rust toolchain version.`);const n=`crate-versions:${t}`,e=q(n);if(e)return console.log(`[cache hit] crate-versions ${t}`),b(e);const r=((await U(`${B}/crates/${t}/versions`)).versions??[]).map(c=>({num:c.num,yanked:c.yanked,created_at:c.created_at,license:c.license??""}));if(!r.length)return b(`No versions found for "${t}".`);const l=r.map(c=>{const h=c.created_at.slice(0,10),p=c.yanked?" [YANKED]":"";return` ${c.num} ${h}${p}`}),i=[`# ${t} — ${r.length} versions`,"",...l].join(`
52
+ `);return M(n,i),b(i)}catch(n){return L(`Could not fetch versions for "${t}". ${n.message}
53
+ Tip: verify the crate exists with search_crates({ query: "${t}" }).`)}})}function Mt(s){s.tool("get_source_code","Fetch the source code of a Rust item from docs.rs. Returns the raw source implementation.",{crateName:y.string().describe("Crate name"),path:y.string().describe('Source path relative to crate root (e.g. "src/lib.rs", "src/sync/mutex.rs")'),version:A},{readOnlyHint:!0},async({crateName:t,path:n,version:e})=>{try{const o=n.replace(/^src\//,"");if(F(t)){const h=`https://doc.rust-lang.org/stable/src/${t}/${o}.html`,p=await D(h),m=p("#source-code").text().trim()||p("pre.rust").text().trim();return m?b([`# Source: ${t}/${n}`,h,"","```rust",H(m,12e3),"```"].join(`
54
+ `)):L(`No source code found at ${h}`)}const l=`${rt}/${t}/${e??"latest"}/src/${ct(t)}/${o}.html`,i=await D(l),c=i("#source-code").text().trim()||i("pre.rust").text().trim()||i(".src-line-numbers + code").text().trim();return c?b([`# Source: ${t}/${n}`,l,"","```rust",H(c,12e3),"```"].join(`
55
+ `)):L(`No source code found at ${l}. Check that the path is correct.
56
+ Tip: paths are relative to crate root. Common files: "src/lib.rs", "src/main.rs".`)}catch(o){return L(`Could not fetch source for "${t}/${n}". ${o.message}
57
+ Tip: paths are relative to crate root (e.g. "src/lib.rs"). Check the file exists in the crate.`)}})}const Ht=y.object({itemType:Q.describe("Item type"),itemName:y.string().describe("Item name"),modulePath:y.string().optional().describe("Dot-separated module path")});function Ot(s){s.tool("batch_lookup","Look up multiple items in a single call. Returns a compact summary (signature + short doc) for each item. Saves round-trips when you need several items.",{crateName:y.string().describe("Crate name"),items:y.array(Ht).min(1).max(20).describe("Items to look up (max 20)"),version:A},{readOnlyHint:!0},async({crateName:t,items:n,version:e})=>{const o=e??"latest",r=[`# Batch lookup: ${t} (${n.length} items)`,""],l=await Promise.allSettled(n.map(async({itemType:i,itemName:c,modulePath:h})=>{let p=h;if(p===void 0){const k=await z(t,c,o),_=k.find(x=>x.bareName.toLowerCase()===c.toLowerCase()&&x.type===i)??k.find(x=>x.bareName.toLowerCase()===c.toLowerCase());_&&(p=_.modulePath||void 0)}const m=V(p),f=i==="mod"?`${m}${c}/index.html`:`${m}${J[i]??`${i}.`}${c}.html`,a=j(t,f,o),u=await D(a),d=`${t}::${N(p)}${c}`,$=u("pre.rust.item-decl").text().trim(),g=st(u),v=H(X(u("details.toggle.top-doc").html()??""),500);return{itemType:i,fullName:d,decl:$,featureGate:g,doc:v,url:a}}));for(let i=0;i<l.length;i++){const c=l[i],{itemType:h,itemName:p}=n[i];if(c.status==="fulfilled"){const{fullName:m,decl:f,featureGate:a,doc:u,url:d}=c.value;r.push(`## ${h} ${m}`,d),a&&r.push(`> ${a}`),f&&r.push("```rust",f,"```"),u&&r.push(u),r.push("")}else r.push(`## ${h} ${p}`,` Error: ${c.reason?.message??"unknown error"}`,` Tip: verify with search_crate({ crateName: "${t}", query: "${p}" })`,"")}return b(r.join(`
58
+ `))})}function qt(s){const t=s.match(/github\.com\/([^/]+\/[^/]+)/);return t?t[1].replace(/\.git$/,""):null}function Gt(s){s.tool("get_crate_changelog","Fetch recent GitHub releases for a crate. Requires the crate to have a GitHub repository link on crates.io.",{crateName:y.string().describe("Crate name"),count:y.number().min(1).max(20).optional().describe("Number of releases to fetch (default 5, max 20)")},{readOnlyHint:!0},async({crateName:t,count:n})=>{const e=n??5;try{if(F(t))return b(`"${t}" is part of the Rust standard library.
59
+ See https://github.com/rust-lang/rust/blob/master/RELEASES.md for changelogs.`);const o=`changelog:${t}:${e}`,r=q(o);if(r)return console.log(`[cache hit] changelog ${t}`),b(r);const l=await et(t);if(!l.repository)return L(`No repository link found for "${t}" on crates.io.
60
+ Tip: check get_crate_metadata({ crateName: "${t}" }) to see available links.`);const i=qt(l.repository);if(!i)return L(`Repository "${l.repository}" is not a GitHub URL.`);const c=await U(`https://api.github.com/repos/${i}/releases?per_page=${e}`);if(!c.length)return b(`No GitHub releases found for ${i}.`);const h=[`# ${t} — recent releases (${i})`,""];for(const m of c){const f=m.published_at?.slice(0,10)??"unknown",a=m.name||m.tag_name,u=m.body?H(m.body.trim(),1e3):"(no release notes)";h.push(`## ${a} (${f})`,u,"")}const p=h.join(`
61
+ `);return M(o,p),b(p)}catch(o){return L(`Could not fetch changelog for "${t}". ${o.message}
62
+ Tip: this requires a GitHub repository link on crates.io.`)}})}function Ft(s){s.tool("resolve_type",'Resolve a Rust type path (e.g. "tokio::sync::Mutex" or "std::collections::HashMap") to its documentation. Parses the path to determine the crate, module, and item name automatically.',{typePath:y.string().describe('Full Rust type path (e.g. "tokio::sync::Mutex", "std::collections::HashMap")'),version:A},{readOnlyHint:!0},async({typePath:t,version:n})=>{try{const e=t.split("::").filter(Boolean);if(e.length<2)return L(`Type path "${t}" must have at least a crate and item name (e.g. "serde::Serialize").
63
+ Tip: use search_crate to find the full path of an item.`);const o=e[0],r=e[e.length-1],l=n??"latest",i=await z(o,r,l),c=i.find(x=>{const R=x.name.replace(/::/g,"::"),E=e.slice(1).join("::");return R===E&&x.bareName===r})??i.find(x=>x.bareName===r);if(!c){const x=i.slice(0,10).map(E=>` [${E.type}] ${o}::${E.name}`),R=[`Could not resolve "${t}".`];return x.length?R.push("","Similar items found:",...x):R.push("",`Tip: verify the crate with search_crates({ query: "${o}" }).`),b(R.join(`
64
+ `))}const h=c.modulePath?c.modulePath.replace(/\./g,"/")+"/":"",p=J[c.type]??`${c.type}.`,m=c.type==="mod"?`${h}${r}/index.html`:`${h}${p}${r}.html`,f=j(o,m,l),a=await D(f),u=a("pre.rust.item-decl").text().trim(),d=st(a),$=dt(a),g=ht(a),v=H(X(a("details.toggle.top-doc").html()??""),2e3),k=`${o}::${c.name}`,_=[`# ${c.type} ${k}`,f,""];return $&&_.push(`> DEPRECATED: ${$}`,""),g&&_.push(`> ${g}`,""),d&&_.push(`> ${d}`,""),u&&_.push("```rust",u,"```",""),v&&_.push(v),b(_.join(`
65
+ `))}catch(e){return L(`Could not resolve "${t}". ${e.message}
66
+ Tip: check that the crate exists and the path is correct.`)}})}function Ut(s){s.tool("list_methods","List methods of a struct, enum, or trait with signatures, deprecation status, and short descriptions.",{crateName:y.string().describe("Crate name"),itemType:Q.describe("Item type (struct, enum, or trait)"),itemName:y.string().describe('Item name (e.g. "HashMap", "Iterator")'),modulePath:y.string().optional().describe('Dot-separated module path (e.g. "collections"). Auto-discovered if omitted.'),version:A},{readOnlyHint:!0},async({crateName:t,itemType:n,itemName:e,modulePath:o,version:r})=>{try{const l=r??"latest";let i=o;if(i===void 0){const d=await z(t,e,l),$=d.find(g=>g.bareName.toLowerCase()===e.toLowerCase()&&g.type===n)??d.find(g=>g.bareName.toLowerCase()===e.toLowerCase());$&&(i=$.modulePath||void 0)}const h=`${V(i)}${J[n]??`${n}.`}${e}.html`,p=j(t,h,l),m=await D(p),f=kt(m),a=`${t}::${N(i)}${e}`;if(!f.length)return b(`No methods found for ${n} ${a}.
67
+ ${p}`);const u=[`# Methods of ${n} ${a} (${f.length})`,p,""];for(const d of f){const $=[];d.deprecated&&$.push("DEPRECATED"),d.featureGate&&$.push(`feature: ${d.featureGate}`);const g=$.length?` [${$.join(", ")}]`:"";u.push(`### ${d.name}${g}`),u.push("```rust",d.signature,"```"),d.shortDoc&&u.push(d.shortDoc),u.push("")}return b(u.join(`
68
+ `))}catch(l){return L(`Could not list methods for ${n} "${e}". ${l.message}
69
+ Tip: verify the item exists with search_crate({ crateName: "${t}", query: "${e}" }).`)}})}console.log=(...s)=>console.error(...s);const Wt=["Rust documentation server for docs.rs, crates.io, and the standard library (std/core/alloc).","","Recommended workflow:","1. Discovery: search_crates to find crates, get_crate_brief for a quick overview","2. Navigation: get_crate_items to list module contents, search_crate to find items by name","3. Details: lookup_crate_item for full docs (use includeExamples/includeImpls for extras)","4. Methods: list_methods to see all methods on a struct/enum/trait",'5. Shortcuts: resolve_type to go from a full path like "tokio::sync::Mutex" directly to docs',"6. Batch: batch_lookup to fetch multiple items in one call","","Tips:",'- modulePath uses dots not colons: "sync.mpsc" not "sync::mpsc"',"- lookup_crate_item auto-discovers modulePath when omitted",'- get_source_code paths are relative to crate root (e.g. "src/lib.rs")'].join(`
70
+ `),T=new pt({name:"rust-docs",version:"5.0.0"},{instructions:Wt});St(T);Lt(T);Et(T);It(T);Dt(T);Pt(T);jt(T);At(T);Mt(T);Ot(T);Gt(T);Ft(T);Ut(T);T.prompt("lookup_crate_docs",{crateName:y.string().describe("Crate name")},({crateName:s})=>({messages:[{role:"user",content:{type:"text",text:[`Analyze the documentation for the Rust crate '${s}'. Focus on:`,"1. Main purpose and features","2. Key types and functions","3. Common usage patterns","4. Important notes or warnings","5. Latest version"].join(`
71
+ `)}}]}));async function Kt(){const s=new ft;await T.connect(s)}Kt().catch(s=>{console.error("Failed to start server:",s),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-rustdoc",
3
- "version": "4.0.2",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for fetching and browsing Rust crate documentation from docs.rs and crates.io",
6
6
  "main": "dist/index.js",