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.
- package/README.md +51 -1
- package/dist/index.js +109 -9
- 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
|
|
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.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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);
|