mcp-rustdoc 4.0.1 → 4.0.2
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 +41 -326
- package/dist/index.js +17 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,28 @@
|
|
|
1
1
|
# mcp-rustdoc
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Zero external HTTP dependencies — uses native `fetch` (Node.js >= 20). Responses are cached with LRU eviction (500 entries, 5-minute TTL, stale-while-revalidate) and all HTTP calls retry on transient failures.
|
|
3
|
+
MCP server for browsing Rust crate documentation. Scrapes docs.rs, doc.rust-lang.org (`std`/`core`/`alloc`), and the crates.io API. Zero HTTP dependencies — native `fetch` only.
|
|
6
4
|
|
|
7
5
|
## Tools
|
|
8
6
|
|
|
9
|
-
| Tool |
|
|
7
|
+
| Tool | Description |
|
|
10
8
|
|---|---|
|
|
11
|
-
| `get_crate_metadata` | Version, features, deps, MSRV, links
|
|
12
|
-
| `get_crate_brief` |
|
|
13
|
-
| `lookup_crate_docs` | Crate overview
|
|
14
|
-
| `get_crate_items` | Items in a module
|
|
15
|
-
| `lookup_crate_item` |
|
|
16
|
-
| `search_crate` | Ranked symbol search
|
|
17
|
-
| `search_crates` | Search crates.io by keyword
|
|
18
|
-
| `get_crate_versions` | All published versions with dates and yanked status
|
|
19
|
-
| `get_source_code` | Raw source code
|
|
20
|
-
| `batch_lookup` |
|
|
21
|
-
| `get_crate_changelog` |
|
|
22
|
-
| `resolve_type` | Resolve a
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Standard library support
|
|
27
|
-
|
|
28
|
-
All documentation tools work with `std`, `core`, and `alloc` — the Rust standard library crates hosted at `doc.rust-lang.org`. Use them exactly like any other crate:
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
> lookup_crate_docs({ crateName: "std" })
|
|
32
|
-
> get_crate_items({ crateName: "std", modulePath: "collections" })
|
|
33
|
-
> search_crate({ crateName: "core", query: "Option" })
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`get_crate_metadata` returns a helpful message for std crates since they aren't published on crates.io.
|
|
9
|
+
| `get_crate_metadata` | Version, features, deps, MSRV, links |
|
|
10
|
+
| `get_crate_brief` | Metadata + overview + modules + focused module items in one call |
|
|
11
|
+
| `lookup_crate_docs` | Crate overview, version, sections, re-exports |
|
|
12
|
+
| `get_crate_items` | Items in a module. Filter by type and feature gate. |
|
|
13
|
+
| `lookup_crate_item` | Full item docs: signature, methods, variants, impls, examples. Auto-discovers module path. |
|
|
14
|
+
| `search_crate` | Ranked symbol search within a crate |
|
|
15
|
+
| `search_crates` | Search crates.io by keyword |
|
|
16
|
+
| `get_crate_versions` | All published versions with dates and yanked status |
|
|
17
|
+
| `get_source_code` | Raw source code from docs.rs |
|
|
18
|
+
| `batch_lookup` | Multiple item lookups in one call (up to 20) |
|
|
19
|
+
| `get_crate_changelog` | GitHub releases for a crate |
|
|
20
|
+
| `resolve_type` | Resolve a type path like `tokio::sync::Mutex` to its docs |
|
|
21
|
+
|
|
22
|
+
All tools accept an optional `version` parameter.
|
|
37
23
|
|
|
38
24
|
## Install
|
|
39
25
|
|
|
40
|
-
No clone needed. Just configure your AI coding assistant with `npx`:
|
|
41
|
-
|
|
42
26
|
```
|
|
43
27
|
npx -y mcp-rustdoc
|
|
44
28
|
```
|
|
@@ -49,33 +33,10 @@ npx -y mcp-rustdoc
|
|
|
49
33
|
claude mcp add mcp-rustdoc -- npx -y mcp-rustdoc
|
|
50
34
|
```
|
|
51
35
|
|
|
52
|
-
Or add to your project's `.mcp.json`:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"mcpServers": {
|
|
57
|
-
"mcp-rustdoc": {
|
|
58
|
-
"type": "stdio",
|
|
59
|
-
"command": "npx",
|
|
60
|
-
"args": ["-y", "mcp-rustdoc"]
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
36
|
### Gemini CLI
|
|
67
37
|
|
|
68
|
-
Add to `~/.gemini/settings.json` (global) or `.gemini/settings.json` (project):
|
|
69
|
-
|
|
70
38
|
```json
|
|
71
|
-
{
|
|
72
|
-
"mcpServers": {
|
|
73
|
-
"mcp-rustdoc": {
|
|
74
|
-
"command": "npx",
|
|
75
|
-
"args": ["-y", "mcp-rustdoc"]
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
39
|
+
{ "mcpServers": { "mcp-rustdoc": { "command": "npx", "args": ["-y", "mcp-rustdoc"] } } }
|
|
79
40
|
```
|
|
80
41
|
|
|
81
42
|
### OpenAI Codex CLI
|
|
@@ -84,295 +45,49 @@ Add to `~/.gemini/settings.json` (global) or `.gemini/settings.json` (project):
|
|
|
84
45
|
codex mcp add mcp-rustdoc -- npx -y mcp-rustdoc
|
|
85
46
|
```
|
|
86
47
|
|
|
87
|
-
Or add to `~/.codex/config.toml`:
|
|
88
|
-
|
|
89
|
-
```toml
|
|
90
|
-
[mcp_servers.mcp-rustdoc]
|
|
91
|
-
command = "npx"
|
|
92
|
-
args = ["-y", "mcp-rustdoc"]
|
|
93
|
-
```
|
|
94
|
-
|
|
95
48
|
### Claude Desktop
|
|
96
49
|
|
|
97
|
-
Add to your `claude_desktop_config.json`:
|
|
98
|
-
|
|
99
50
|
```json
|
|
100
|
-
{
|
|
101
|
-
"mcpServers": {
|
|
102
|
-
"mcp-rustdoc": {
|
|
103
|
-
"command": "npx",
|
|
104
|
-
"args": ["-y", "mcp-rustdoc"]
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
51
|
+
{ "mcpServers": { "mcp-rustdoc": { "command": "npx", "args": ["-y", "mcp-rustdoc"] } } }
|
|
108
52
|
```
|
|
109
53
|
|
|
110
|
-
|
|
54
|
+
## Parameters
|
|
111
55
|
|
|
112
|
-
|
|
113
|
-
npx @modelcontextprotocol/inspector -- npx -y mcp-rustdoc
|
|
114
|
-
```
|
|
56
|
+
Every tool takes `crateName` (string, required) and `version` (string, optional). Additional parameters:
|
|
115
57
|
|
|
116
|
-
|
|
58
|
+
| Tool | Extra parameters |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `get_crate_brief` | `focusModules` — comma-separated modules to expand |
|
|
61
|
+
| `get_crate_items` | `modulePath`, `itemType`, `feature` |
|
|
62
|
+
| `lookup_crate_item` | `itemType` (required), `itemName` (required), `modulePath`, `includeImpls`, `includeExamples` |
|
|
63
|
+
| `search_crate` | `query` (required) |
|
|
64
|
+
| `search_crates` | `query` (required), `page`, `perPage` |
|
|
65
|
+
| `get_source_code` | `path` (required) |
|
|
66
|
+
| `batch_lookup` | `items` (required) — array of `{ itemType, itemName, modulePath? }`, max 20 |
|
|
67
|
+
| `get_crate_changelog` | `count` — number of releases (default 5) |
|
|
68
|
+
| `resolve_type` | `typePath` (required) — e.g. `"tokio::sync::Mutex"` |
|
|
117
69
|
|
|
118
70
|
## Development
|
|
119
71
|
|
|
120
72
|
```bash
|
|
121
73
|
git clone https://github.com/kieled/mcp-rustdoc.git
|
|
122
|
-
cd mcp-rustdoc
|
|
123
|
-
npm install
|
|
74
|
+
cd mcp-rustdoc && npm install
|
|
124
75
|
```
|
|
125
76
|
|
|
126
|
-
### Run with bun (no build step)
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
bun run dev
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Build with Vite and run with Node.js
|
|
133
|
-
|
|
134
77
|
```bash
|
|
135
|
-
|
|
136
|
-
|
|
78
|
+
bun run dev # run with bun (no build)
|
|
79
|
+
npm run build # vite build → dist/index.js
|
|
80
|
+
npm run typecheck # tsc --noEmit
|
|
81
|
+
npm publish # build + publish
|
|
137
82
|
```
|
|
138
83
|
|
|
139
|
-
### Build with tsc
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
npm run build:tsc # tsc → dist/ (one .js per source file)
|
|
143
|
-
node dist/index.js
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Type check only
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
npm run typecheck # tsc --noEmit
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Publish
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
npm publish # runs vite build via prepublishOnly, then publishes
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Tool reference
|
|
161
|
-
|
|
162
|
-
### `get_crate_metadata`
|
|
163
|
-
|
|
164
|
-
Fetches structured metadata from the crates.io API.
|
|
165
|
-
|
|
166
|
-
| Parameter | Type | Required | Description |
|
|
167
|
-
|---|---|---|---|
|
|
168
|
-
| `crateName` | string | yes | Crate name |
|
|
169
|
-
| `version` | string | no | Pinned version |
|
|
170
|
-
|
|
171
|
-
Returns: version, description, links (docs/repo/crates.io), license, download count, MSRV, full feature list with activations, optional deps (feature-gated), required deps.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
### `get_crate_brief`
|
|
176
|
-
|
|
177
|
-
Single call to bootstrap context for a crate. Combines metadata, overview docs, re-exports, module list, and optionally expands focused modules.
|
|
178
|
-
|
|
179
|
-
| Parameter | Type | Required | Description |
|
|
180
|
-
|---|---|---|---|
|
|
181
|
-
| `crateName` | string | yes | Crate name |
|
|
182
|
-
| `version` | string | no | Pinned version |
|
|
183
|
-
| `focusModules` | string | no | Comma-separated modules to expand (e.g. `"sync,task"`) |
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
### `lookup_crate_docs`
|
|
188
|
-
|
|
189
|
-
Fetches the main documentation page for a crate.
|
|
190
|
-
|
|
191
|
-
| Parameter | Type | Required | Description |
|
|
192
|
-
|---|---|---|---|
|
|
193
|
-
| `crateName` | string | yes | Crate name |
|
|
194
|
-
| `version` | string | no | Pinned version |
|
|
195
|
-
|
|
196
|
-
Returns: crate version, overview documentation text, re-exports, and section list with item counts.
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
### `get_crate_items`
|
|
201
|
-
|
|
202
|
-
Lists all public items in a crate root or specific module. Supports filtering by item type and feature gate.
|
|
203
|
-
|
|
204
|
-
| Parameter | Type | Required | Description |
|
|
205
|
-
|---|---|---|---|
|
|
206
|
-
| `crateName` | string | yes | Crate name |
|
|
207
|
-
| `modulePath` | string | no | Dot-separated path (e.g. `"sync"`, `"io.util"`) |
|
|
208
|
-
| `itemType` | enum | no | Filter: `mod` `struct` `enum` `trait` `fn` `macro` `type` `constant` `static` `union` `attr` `derive` |
|
|
209
|
-
| `feature` | string | no | Filter to items behind this feature gate (e.g. `"sync"`, `"fs"`) |
|
|
210
|
-
| `version` | string | no | Pinned version |
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
### `lookup_crate_item`
|
|
215
|
-
|
|
216
|
-
Retrieves full documentation for a single item. Auto-discovers the module path if omitted by searching `all.html`. Falls back to fuzzy suggestions if the exact item is not found.
|
|
217
|
-
|
|
218
|
-
| Parameter | Type | Required | Description |
|
|
219
|
-
|---|---|---|---|
|
|
220
|
-
| `crateName` | string | yes | Crate name |
|
|
221
|
-
| `itemType` | enum | yes | Item type (see `get_crate_items`) |
|
|
222
|
-
| `itemName` | string | yes | Item name (e.g. `"Mutex"`, `"spawn"`) |
|
|
223
|
-
| `modulePath` | string | no | Dot-separated module path. Auto-discovered if omitted. |
|
|
224
|
-
| `version` | string | no | Pinned version |
|
|
225
|
-
| `includeImpls` | boolean | no | Include trait implementation list |
|
|
226
|
-
| `includeExamples` | boolean | no | Include code examples |
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
### `search_crate`
|
|
231
|
-
|
|
232
|
-
Searches all items in a crate by name. Results are ranked: exact match on the bare item name scores highest, then prefix matches, then substring matches.
|
|
233
|
-
|
|
234
|
-
| Parameter | Type | Required | Description |
|
|
235
|
-
|---|---|---|---|
|
|
236
|
-
| `crateName` | string | yes | Crate name |
|
|
237
|
-
| `query` | string | yes | Search query (case-insensitive) |
|
|
238
|
-
| `version` | string | no | Pinned version |
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
### `search_crates`
|
|
243
|
-
|
|
244
|
-
Search for Rust crates on crates.io by keyword.
|
|
245
|
-
|
|
246
|
-
| Parameter | Type | Required | Description |
|
|
247
|
-
|---|---|---|---|
|
|
248
|
-
| `query` | string | yes | Search keywords |
|
|
249
|
-
| `page` | number | no | Page number (default 1) |
|
|
250
|
-
| `perPage` | number | no | Results per page (default 10, max 50) |
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
### `get_crate_versions`
|
|
255
|
-
|
|
256
|
-
List all published versions of a crate from crates.io.
|
|
257
|
-
|
|
258
|
-
| Parameter | Type | Required | Description |
|
|
259
|
-
|---|---|---|---|
|
|
260
|
-
| `crateName` | string | yes | Crate name |
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
### `get_source_code`
|
|
265
|
-
|
|
266
|
-
Fetch the raw source code of a file from docs.rs (or doc.rust-lang.org for std crates).
|
|
267
|
-
|
|
268
|
-
| Parameter | Type | Required | Description |
|
|
269
|
-
|---|---|---|---|
|
|
270
|
-
| `crateName` | string | yes | Crate name |
|
|
271
|
-
| `path` | string | yes | Source path relative to crate root (e.g. `"src/lib.rs"`, `"src/sync/mutex.rs"`) |
|
|
272
|
-
| `version` | string | no | Pinned version |
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
### `batch_lookup`
|
|
277
|
-
|
|
278
|
-
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 at once.
|
|
279
|
-
|
|
280
|
-
| Parameter | Type | Required | Description |
|
|
281
|
-
|---|---|---|---|
|
|
282
|
-
| `crateName` | string | yes | Crate name |
|
|
283
|
-
| `items` | array | yes | Items to look up (max 20). Each: `{ itemType, itemName, modulePath? }` |
|
|
284
|
-
| `version` | string | no | Pinned version |
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
### `get_crate_changelog`
|
|
289
|
-
|
|
290
|
-
Fetch recent GitHub releases for a crate. Requires the crate to have a GitHub repository link on crates.io.
|
|
291
|
-
|
|
292
|
-
| Parameter | Type | Required | Description |
|
|
293
|
-
|---|---|---|---|
|
|
294
|
-
| `crateName` | string | yes | Crate name |
|
|
295
|
-
| `count` | number | no | Number of releases (default 5, max 20) |
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
### `resolve_type`
|
|
300
|
-
|
|
301
|
-
Resolve a full Rust type path to its documentation. Parses the path to determine the crate, module, and item name automatically.
|
|
302
|
-
|
|
303
|
-
| Parameter | Type | Required | Description |
|
|
304
|
-
|---|---|---|---|
|
|
305
|
-
| `typePath` | string | yes | Full Rust type path (e.g. `"tokio::sync::Mutex"`, `"std::collections::HashMap"`) |
|
|
306
|
-
| `version` | string | no | Pinned version |
|
|
307
|
-
|
|
308
|
-
---
|
|
309
|
-
|
|
310
|
-
## Recommended workflows
|
|
311
|
-
|
|
312
|
-
### Exploring a new crate
|
|
313
|
-
|
|
314
|
-
1. `get_crate_brief` with `focusModules` targeting the modules you care about
|
|
315
|
-
2. `search_crate` to find specific types or functions
|
|
316
|
-
3. `lookup_crate_item` for detailed signatures and docs (no need to specify `modulePath` — it auto-discovers)
|
|
317
|
-
|
|
318
|
-
### Understanding feature flags
|
|
319
|
-
|
|
320
|
-
1. `get_crate_metadata` to see all features, their activations, and MSRV
|
|
321
|
-
2. `get_crate_items` with the `feature` parameter to see items behind a specific feature gate
|
|
322
|
-
|
|
323
|
-
### Finding the right type
|
|
324
|
-
|
|
325
|
-
1. `search_crate` with a keyword
|
|
326
|
-
2. `lookup_crate_item` with `includeImpls: true` to see what traits it implements
|
|
327
|
-
3. `resolve_type` to chase cross-crate type paths (e.g. `tokio::sync::Mutex`)
|
|
328
|
-
|
|
329
|
-
### Bulk lookups
|
|
330
|
-
|
|
331
|
-
1. `batch_lookup` to fetch signatures and docs for multiple items in one call
|
|
332
|
-
2. `get_crate_changelog` to see what changed in recent releases
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
84
|
## Architecture
|
|
337
85
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
tools/
|
|
344
|
-
shared.ts Shared Zod schemas (itemTypeEnum, versionParam)
|
|
345
|
-
lookup-docs.ts lookup_crate_docs
|
|
346
|
-
get-items.ts get_crate_items
|
|
347
|
-
lookup-item.ts lookup_crate_item
|
|
348
|
-
search.ts search_crate
|
|
349
|
-
search-crates.ts search_crates
|
|
350
|
-
crate-versions.ts get_crate_versions
|
|
351
|
-
source-code.ts get_source_code
|
|
352
|
-
crate-metadata.ts get_crate_metadata
|
|
353
|
-
crate-brief.ts get_crate_brief
|
|
354
|
-
batch-lookup.ts batch_lookup
|
|
355
|
-
crate-changelog.ts get_crate_changelog
|
|
356
|
-
resolve-type.ts resolve_type
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Data sources
|
|
360
|
-
|
|
361
|
-
- **docs.rs** — HTML pages parsed with cheerio for surgical DOM extraction (only the elements needed, not full-page conversion)
|
|
362
|
-
- **doc.rust-lang.org** — Same rustdoc HTML format, used for `std`, `core`, and `alloc`
|
|
363
|
-
- **crates.io API** — JSON endpoints for metadata, features, dependencies, and search
|
|
364
|
-
- **GitHub API** — Release notes for changelogs (via repository link from crates.io)
|
|
365
|
-
|
|
366
|
-
### Design decisions
|
|
367
|
-
|
|
368
|
-
- **Native fetch, zero HTTP deps** — Uses Node.js built-in `fetch` with `AbortController` timeouts. No axios, no node-fetch.
|
|
369
|
-
- **cheerio for DOM extraction** — Extracts only specific DOM elements (`.item-decl`, `.top-doc`, `.code-header`, `.stab.portability`) to minimize token usage. Also powers `cleanHtml()` as a replacement for `html-to-text`.
|
|
370
|
-
- **LRU cache with stale-while-revalidate** — 500-entry cap with LRU eviction. Stale entries (past 5-min TTL but within 15-min grace) are served immediately while a background refresh runs.
|
|
371
|
-
- **Retry with backoff** — Transient 5xx errors are retried up to 2 times with exponential backoff.
|
|
372
|
-
- **Ranked search** — `all.html` contains every public item; scoring by exact/prefix/substring gives better results than flat substring matching.
|
|
373
|
-
- **Auto-discovery** — `lookup_crate_item` searches `all.html` to find the module path when not provided, and falls back to fuzzy suggestions when an exact match fails.
|
|
374
|
-
- **Version parameter everywhere** — Agents working on projects with pinned dependencies can read docs for specific versions.
|
|
375
|
-
- **Optional sections** — `includeImpls` and `includeExamples` default to off so the base response stays compact; agents opt in when they need more detail.
|
|
86
|
+
- **cheerio** — surgical DOM extraction from rustdoc HTML
|
|
87
|
+
- **Native fetch** — `AbortController` timeouts, retry with backoff on 5xx
|
|
88
|
+
- **LRU cache** — 500 entries, 5-min TTL, 15-min stale-while-revalidate
|
|
89
|
+
- **Ranked search** — exact > prefix > substring scoring on `all.html`
|
|
90
|
+
- **Auto-discovery** — `lookup_crate_item` finds module paths via `all.html`, fuzzy fallback on miss
|
|
376
91
|
|
|
377
92
|
## License
|
|
378
93
|
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
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,
|
|
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(`
|
|
3
3
|
`)}),t("body").text().replace(/\n{3,}/g,`
|
|
4
4
|
|
|
5
5
|
`).trim()}function T(e,t){return e.length>t?e.slice(0,t)+`
|
|
6
6
|
|
|
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,
|
|
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:
|
|
9
|
-
`)):v(`No items found in ${
|
|
10
|
-
`))}throw $}const u=`${t}::${rt(d)}${s}`,m=
|
|
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
|
|
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
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,[
|
|
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
|
|
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,
|
|
16
|
-
`);return P(l,
|
|
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
|
|
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{if(G(t)){const
|
|
19
|
-
`)):k(`No source code found at ${
|
|
20
|
-
`)):k(`No source code found at ${
|
|
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
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}`,
|
|
23
|
-
`);return P(n,
|
|
24
|
-
`))}const d=c.modulePath?c.modulePath.replace(/\./g,"/")+"/":"",
|
|
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
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
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)});
|