mcp-rustdoc 3.0.0 → 3.1.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 (3) hide show
  1. package/README.md +51 -1
  2. package/dist/index.js +109 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mcp-rustdoc
2
2
 
3
- An MCP server that gives AI assistants deep access to the Rust ecosystem. It scrapes docs.rs (and `doc.rust-lang.org` for `std`/`core`/`alloc`) with surgical DOM extraction (cheerio) and queries the crates.io API, exposing seven tools that cover everything from high-level crate overviews to individual method signatures, feature gates, trait impls, and code examples. Responses are cached in memory (5-minute TTL) to avoid redundant fetches.
3
+ An MCP server that gives AI assistants deep access to the Rust ecosystem. It scrapes docs.rs (and `doc.rust-lang.org` for `std`/`core`/`alloc`) with surgical DOM extraction (cheerio) and queries the crates.io API, exposing nine tools that cover everything from high-level crate overviews to individual method signatures, feature gates, trait impls, and code examples. Responses are cached in memory (5-minute TTL) to avoid redundant fetches.
4
4
 
5
5
  ## Tools
6
6
 
@@ -13,6 +13,8 @@ An MCP server that gives AI assistants deep access to the Rust ecosystem. It scr
13
13
  | `lookup_crate_item` | Item detail: signature, docs, methods, variants, optionally trait impls + examples |
14
14
  | `search_crate` | Ranked symbol search (exact > prefix > substring) with canonical paths |
15
15
  | `search_crates` | Search crates.io by keyword — returns name, description, downloads, version |
16
+ | `get_crate_versions` | All published versions with dates and yanked status (crates.io API) |
17
+ | `get_source_code` | Raw source code of a file from docs.rs or doc.rust-lang.org |
16
18
 
17
19
  Every tool accepts an optional `version` parameter to pin a specific crate version instead of `latest`.
18
20
 
@@ -371,6 +373,52 @@ Search for Rust crates on crates.io by keyword.
371
373
 
372
374
  ---
373
375
 
376
+ ### `get_crate_versions`
377
+
378
+ List all published versions of a crate from crates.io.
379
+
380
+ | Parameter | Type | Required | Description |
381
+ |---|---|---|---|
382
+ | `crateName` | string | yes | Crate name |
383
+
384
+ ```
385
+ > get_crate_versions({ crateName: "serde" })
386
+
387
+ # serde — 312 versions
388
+
389
+ 1.0.219 2025-02-01
390
+ 1.0.218 2025-01-12
391
+ 1.0.217 2024-12-23
392
+ ...
393
+ 0.1.0 2014-12-09
394
+ ```
395
+
396
+ ---
397
+
398
+ ### `get_source_code`
399
+
400
+ Fetch the raw source code of a file from docs.rs (or doc.rust-lang.org for std crates).
401
+
402
+ | Parameter | Type | Required | Description |
403
+ |---|---|---|---|
404
+ | `crateName` | string | yes | Crate name |
405
+ | `path` | string | yes | Source path relative to crate root (e.g. `"src/lib.rs"`, `"src/sync/mutex.rs"`) |
406
+ | `version` | string | no | Pinned version |
407
+
408
+ ```
409
+ > get_source_code({ crateName: "tokio", path: "src/sync/mutex.rs" })
410
+
411
+ # Source: tokio/src/sync/mutex.rs
412
+ https://docs.rs/tokio/latest/src/tokio/sync/mutex.rs
413
+
414
+ \`\`\`rust
415
+ use crate::sync::batch_semaphore as semaphore;
416
+ ...
417
+ \`\`\`
418
+ ```
419
+
420
+ ---
421
+
374
422
  ## Recommended workflows
375
423
 
376
424
  ### Exploring a new crate
@@ -408,6 +456,8 @@ src/
408
456
  lookup-item.ts lookup_crate_item
409
457
  search.ts search_crate
410
458
  search-crates.ts search_crates
459
+ crate-versions.ts get_crate_versions
460
+ source-code.ts get_source_code
411
461
  crate-metadata.ts get_crate_metadata
412
462
  crate-brief.ts get_crate_brief
413
463
  ```
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ function cacheSet(key, data, ttl = DEFAULT_TTL) {
21
21
  }
22
22
  const DOCS_BASE = "https://docs.rs";
23
23
  const CRATES_IO = "https://crates.io/api/v1";
24
- const USER_AGENT = "mcp-rust-docs/3.0.0";
24
+ const USER_AGENT = "mcp-rust-docs/3.1.0";
25
25
  const MAX_DOC_LENGTH = 6e3;
26
26
  const MAX_SEARCH_RESULTS = 100;
27
27
  const SECTION_TO_TYPE = {
@@ -209,7 +209,7 @@ const itemTypeEnum = z.enum([
209
209
  "derive"
210
210
  ]);
211
211
  const versionParam = z.string().optional().describe('Crate version (e.g. "1.49.0"). Defaults to latest.');
212
- function register$6(server2) {
212
+ function register$8(server2) {
213
213
  server2.tool(
214
214
  "lookup_crate_docs",
215
215
  "Fetch the main documentation for a Rust crate. Returns overview, version, sections, and re-exports.",
@@ -246,7 +246,7 @@ function register$6(server2) {
246
246
  }
247
247
  );
248
248
  }
249
- function register$5(server2) {
249
+ function register$7(server2) {
250
250
  server2.tool(
251
251
  "get_crate_items",
252
252
  "List public items in a crate root or module. Returns names, types, feature gates, and short descriptions.",
@@ -291,7 +291,7 @@ function register$5(server2) {
291
291
  }
292
292
  );
293
293
  }
294
- function register$4(server2) {
294
+ function register$6(server2) {
295
295
  server2.tool(
296
296
  "lookup_crate_item",
297
297
  "Get detailed documentation for a specific item. Returns signature, docs, feature gate, methods, trait impls, and optionally examples.",
@@ -389,7 +389,7 @@ function scoreMatch(name, query) {
389
389
  if (lower.includes(q)) return 20;
390
390
  return 0;
391
391
  }
392
- function register$3(server2) {
392
+ function register$5(server2) {
393
393
  server2.tool(
394
394
  "search_crate",
395
395
  "Search for items by name within a Rust crate. Returns ranked results with canonical paths and item types.",
@@ -433,7 +433,7 @@ function register$3(server2) {
433
433
  }
434
434
  );
435
435
  }
436
- function register$2(server2) {
436
+ function register$4(server2) {
437
437
  server2.tool(
438
438
  "get_crate_metadata",
439
439
  "Get crate metadata from crates.io: version, features, default features, optional dependencies, and links.",
@@ -500,7 +500,7 @@ Use lookup_crate_docs, get_crate_items, lookup_crate_item, or search_crate to br
500
500
  }
501
501
  );
502
502
  }
503
- function register$1(server2) {
503
+ function register$3(server2) {
504
504
  server2.tool(
505
505
  "get_crate_brief",
506
506
  "Bundle call: fetches crate metadata, overview docs, module list, re-exports, and optionally items from focused modules — all in one shot.",
@@ -610,7 +610,7 @@ function register$1(server2) {
610
610
  }
611
611
  );
612
612
  }
613
- function register(server2) {
613
+ function register$2(server2) {
614
614
  server2.tool(
615
615
  "search_crates",
616
616
  "Search for Rust crates on crates.io by keyword. Returns name, description, downloads, and version.",
@@ -658,8 +658,108 @@ function register(server2) {
658
658
  }
659
659
  );
660
660
  }
661
+ function register$1(server2) {
662
+ server2.tool(
663
+ "get_crate_versions",
664
+ "List all published versions of a crate from crates.io, with yanked status and release dates.",
665
+ {
666
+ crateName: z.string().describe("Crate name")
667
+ },
668
+ async ({ crateName }) => {
669
+ try {
670
+ if (isStdCrate(crateName)) {
671
+ return textResult(
672
+ `"${crateName}" is part of the Rust standard library and is not published on crates.io.
673
+ Its version matches the Rust toolchain version.`
674
+ );
675
+ }
676
+ const cacheKey = `crate-versions:${crateName}`;
677
+ const cached = cacheGet(cacheKey);
678
+ if (cached) {
679
+ console.log(`[cache hit] crate-versions ${crateName}`);
680
+ return textResult(cached);
681
+ }
682
+ const { data } = await axios.get(`${CRATES_IO}/crates/${crateName}/versions`, {
683
+ headers: { "User-Agent": USER_AGENT },
684
+ timeout: 1e4
685
+ });
686
+ const versions = (data.versions ?? []).map((v) => ({
687
+ num: v.num,
688
+ yanked: v.yanked,
689
+ created_at: v.created_at,
690
+ license: v.license ?? ""
691
+ }));
692
+ if (!versions.length) {
693
+ return textResult(`No versions found for "${crateName}".`);
694
+ }
695
+ const lines = versions.map((v) => {
696
+ const date = v.created_at.slice(0, 10);
697
+ const yanked = v.yanked ? " [YANKED]" : "";
698
+ return ` ${v.num} ${date}${yanked}`;
699
+ });
700
+ const result = [
701
+ `# ${crateName} — ${versions.length} versions`,
702
+ "",
703
+ ...lines
704
+ ].join("\n");
705
+ cacheSet(cacheKey, result);
706
+ return textResult(result);
707
+ } catch (e) {
708
+ return errorResult(`Could not fetch versions for "${crateName}". ${e.message}`);
709
+ }
710
+ }
711
+ );
712
+ }
713
+ function register(server2) {
714
+ server2.tool(
715
+ "get_source_code",
716
+ "Fetch the source code of a Rust item from docs.rs. Returns the raw source implementation.",
717
+ {
718
+ crateName: z.string().describe("Crate name"),
719
+ path: z.string().describe('Source path relative to crate root (e.g. "src/lib.rs", "src/sync/mutex.rs")'),
720
+ version: versionParam
721
+ },
722
+ async ({ crateName, path, version }) => {
723
+ try {
724
+ if (isStdCrate(crateName)) {
725
+ const url2 = `https://doc.rust-lang.org/stable/src/${crateName}/${path}`;
726
+ const $2 = await fetchDom(url2);
727
+ const code2 = $2("#source-code").text().trim() || $2("pre.rust").text().trim();
728
+ if (!code2) return errorResult(`No source code found at ${url2}`);
729
+ return textResult([
730
+ `# Source: ${crateName}/${path}`,
731
+ url2,
732
+ "",
733
+ "```rust",
734
+ truncate(code2, 12e3),
735
+ "```"
736
+ ].join("\n"));
737
+ }
738
+ const ver = version ?? "latest";
739
+ const url = `${DOCS_BASE}/${crateName}/${ver}/src/${crateSlug(crateName)}/${path}`;
740
+ const $ = await fetchDom(url);
741
+ const code = $("#source-code").text().trim() || $("pre.rust").text().trim() || $(".src-line-numbers + code").text().trim();
742
+ if (!code) {
743
+ return errorResult(`No source code found at ${url}. Check that the path is correct.`);
744
+ }
745
+ return textResult([
746
+ `# Source: ${crateName}/${path}`,
747
+ url,
748
+ "",
749
+ "```rust",
750
+ truncate(code, 12e3),
751
+ "```"
752
+ ].join("\n"));
753
+ } catch (e) {
754
+ return errorResult(`Could not fetch source for "${crateName}/${path}". ${e.message}`);
755
+ }
756
+ }
757
+ );
758
+ }
661
759
  console.log = (...args) => console.error(...args);
662
- const server = new McpServer({ name: "rust-docs", version: "3.0.0" });
760
+ const server = new McpServer({ name: "rust-docs", version: "3.1.0" });
761
+ register$8(server);
762
+ register$7(server);
663
763
  register$6(server);
664
764
  register$5(server);
665
765
  register$4(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-rustdoc",
3
- "version": "3.0.0",
3
+ "version": "3.1.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",