react-native-docs-mcp 0.1.0 → 0.2.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 +31 -0
- package/dist/index.js +207 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,12 +52,15 @@ Edit: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
52
52
|
|
|
53
53
|
## Features
|
|
54
54
|
|
|
55
|
+
- **🔑 No API Key**: Unlike hosted docs services (Context7, GitMCP), everything runs on your machine — no account, no key, no rate limits
|
|
56
|
+
- **🔌 Works Offline**: Clones the official react-native-website docs repo once, then searches locally — no network calls at query time
|
|
55
57
|
- **🔍 Semantic Search**: AI-powered search using embeddings for conceptual matches
|
|
56
58
|
- **⚡ Fast Results**: In-memory vector search with hybrid keyword+semantic ranking
|
|
57
59
|
- **📦 Zero Config**: Works with `npx` - no installation needed
|
|
58
60
|
- **🤖 Local AI**: Runs embeddings locally (no API costs)
|
|
59
61
|
- **📝 Concise Responses**: Returns summaries instead of full documentation
|
|
60
62
|
- **🔄 Auto-sync**: Pulls latest docs from the react-native-website repo automatically
|
|
63
|
+
- **📌 Version Pinning**: `--docs-version=0.77` scopes docs to the React Native release your app actually uses
|
|
61
64
|
|
|
62
65
|
## Usage
|
|
63
66
|
|
|
@@ -88,6 +91,7 @@ Get a specific documentation page.
|
|
|
88
91
|
**Parameters**:
|
|
89
92
|
|
|
90
93
|
- `path` (required): Document path (e.g., "getting-started", "the-new-architecture/using-codegen")
|
|
94
|
+
- `full` (optional): Return the full raw page instead of the ~1500 char summary (default: false)
|
|
91
95
|
|
|
92
96
|
**Example**:
|
|
93
97
|
|
|
@@ -95,6 +99,13 @@ Get a specific documentation page.
|
|
|
95
99
|
Get the React Native flexbox documentation
|
|
96
100
|
```
|
|
97
101
|
|
|
102
|
+
**Why `full`?** The default summary is enough for most reference pages, but long guides — like "The New Architecture" migration docs, or a full native-modules walkthrough — can run well past 1500 chars, and the summary may stop before the part you actually need. Ask for the complete page when that happens:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Get the full page for the-new-architecture/using-codegen, I need every step
|
|
106
|
+
```
|
|
107
|
+
which calls `get_doc` with `{ "path": "the-new-architecture/using-codegen", "full": true }`.
|
|
108
|
+
|
|
98
109
|
#### `list_sections`
|
|
99
110
|
|
|
100
111
|
List all available documentation sections.
|
|
@@ -103,6 +114,26 @@ List all available documentation sections.
|
|
|
103
114
|
|
|
104
115
|
Pull latest documentation from the Git repository.
|
|
105
116
|
|
|
117
|
+
### Docs version
|
|
118
|
+
|
|
119
|
+
By default this server indexes the always-current `docs/` folder from the react-native-website repo — the same docs shown on reactnative.dev today. To pin it to a specific past release's frozen docs snapshot instead, pass `--docs-version`:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx react-native-docs-mcp --docs-version=0.77
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
or set the `REACT_NATIVE_DOCS_VERSION` env var (the CLI flag wins if both are set). With Claude Code:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
claude mcp add --transport stdio react-native-docs -- npx react-native-docs-mcp --docs-version=0.77
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
(Bare `--version` prints the package version, as you'd expect from any CLI.)
|
|
132
|
+
|
|
133
|
+
**Why pin a version?** If your app is running React Native 0.77 but the agent searches always-current docs, it can suggest an API that only exists in 0.86, or miss that something was renamed/removed since your version. Pinning `--docs-version` to match your `react-native` dependency's version keeps suggestions consistent with the APIs actually available in your app — useful when working on an app that's a few releases behind latest, or when debugging something version-specific (e.g. "did this New Architecture behavior change between 0.78 and 0.82?" — run two instances, one per version, and compare).
|
|
134
|
+
|
|
135
|
+
Only `latest` (the default) is fully verified against the current docs structure; older version snapshots are indexed best-effort with the same settings.
|
|
136
|
+
|
|
106
137
|
### Resources
|
|
107
138
|
|
|
108
139
|
The server exposes documentation as resources with the URI pattern:
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,59 @@
|
|
|
3
3
|
// ../../src/config.ts
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
// ../../src/presets/searchDefaults.ts
|
|
8
|
+
var DEFAULT_SEARCH = {
|
|
9
|
+
defaultLimit: 10,
|
|
10
|
+
maxLimit: 50,
|
|
11
|
+
minScore: 0.1,
|
|
12
|
+
semanticSearchEnabled: true,
|
|
13
|
+
semanticMinSimilarity: 0.3,
|
|
14
|
+
hybridKeywordWeight: 0.3,
|
|
15
|
+
hybridSemanticWeight: 0.7
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// ../../src/presets/reactDocs.ts
|
|
19
|
+
var reactDocsPreset = {
|
|
20
|
+
cacheDirName: "react-docs-mcp",
|
|
21
|
+
repoFolderName: "react-dev-repo",
|
|
22
|
+
repo: {
|
|
23
|
+
url: "https://github.com/reactjs/react.dev.git",
|
|
24
|
+
contentPath: "src/content"
|
|
25
|
+
},
|
|
26
|
+
search: { ...DEFAULT_SEARCH },
|
|
27
|
+
server: {
|
|
28
|
+
name: "react-docs-mcp",
|
|
29
|
+
version: "1.2.0"
|
|
30
|
+
},
|
|
31
|
+
sections: ["learn", "reference", "blog", "community"],
|
|
32
|
+
resourceUriScheme: "react-docs",
|
|
33
|
+
docsLabel: "React",
|
|
34
|
+
searchToolName: "search_react_docs",
|
|
35
|
+
searchToolDescription: "Search across React documentation. Returns relevant documentation pages with snippets.",
|
|
36
|
+
pathExample: "reference/react/useState",
|
|
37
|
+
docUrl: { base: "https://react.dev", useFrontmatterId: false },
|
|
38
|
+
sectionResourceOverrides: {
|
|
39
|
+
learn: {
|
|
40
|
+
name: "React Learn Documentation",
|
|
41
|
+
description: "Interactive React tutorial and learning materials"
|
|
42
|
+
},
|
|
43
|
+
reference: {
|
|
44
|
+
name: "React API Reference",
|
|
45
|
+
description: "Complete React API reference documentation"
|
|
46
|
+
},
|
|
47
|
+
blog: {
|
|
48
|
+
name: "React Blog",
|
|
49
|
+
description: "React team blog posts and announcements"
|
|
50
|
+
},
|
|
51
|
+
community: {
|
|
52
|
+
name: "React Community Documentation",
|
|
53
|
+
description: "Community resources, code of conduct, and translations"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ../../src/config.ts
|
|
6
59
|
var getCacheDir = (cacheDirName) => {
|
|
7
60
|
const platform = process.platform;
|
|
8
61
|
const home = homedir();
|
|
@@ -24,66 +77,46 @@ function resolve(preset) {
|
|
|
24
77
|
}
|
|
25
78
|
};
|
|
26
79
|
}
|
|
27
|
-
var
|
|
28
|
-
cacheDirName: "react-docs-mcp",
|
|
29
|
-
repoFolderName: "react-dev-repo",
|
|
30
|
-
repo: {
|
|
31
|
-
url: "https://github.com/reactjs/react.dev.git",
|
|
32
|
-
contentPath: "src/content"
|
|
33
|
-
},
|
|
34
|
-
search: {
|
|
35
|
-
defaultLimit: 10,
|
|
36
|
-
maxLimit: 50,
|
|
37
|
-
minScore: 0.1,
|
|
38
|
-
semanticSearchEnabled: true,
|
|
39
|
-
semanticMinSimilarity: 0.3,
|
|
40
|
-
hybridKeywordWeight: 0.3,
|
|
41
|
-
hybridSemanticWeight: 0.7
|
|
42
|
-
},
|
|
43
|
-
server: {
|
|
44
|
-
name: "react-docs-mcp",
|
|
45
|
-
version: "1.0.0"
|
|
46
|
-
},
|
|
47
|
-
sections: ["learn", "reference", "blog", "community"],
|
|
48
|
-
resourceUriScheme: "react-docs",
|
|
49
|
-
docsLabel: "React",
|
|
50
|
-
searchToolName: "search_react_docs",
|
|
51
|
-
searchToolDescription: "Search across React documentation. Returns relevant documentation pages with snippets.",
|
|
52
|
-
docUrl: { base: "https://react.dev", useFrontmatterId: false }
|
|
53
|
-
};
|
|
54
|
-
var activeConfig = resolve(defaultPreset);
|
|
80
|
+
var activeConfig = resolve(reactDocsPreset);
|
|
55
81
|
function configure(preset) {
|
|
56
82
|
activeConfig = resolve(preset);
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
// ../../src/presets/reactNativeDocs.ts
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
var LATEST_VERSION = "latest";
|
|
87
|
+
function resolveReactNativeDocsPreset(version = LATEST_VERSION) {
|
|
88
|
+
const isLatest = version === LATEST_VERSION;
|
|
89
|
+
if (!isLatest && !/^\d+\.\d+$/.test(version)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Invalid React Native docs version "${version}". Expected a release like "0.77" or "${LATEST_VERSION}".`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
cacheDirName: "react-native-docs-mcp",
|
|
96
|
+
repoFolderName: "react-native-website-repo",
|
|
97
|
+
repo: {
|
|
98
|
+
url: "https://github.com/facebook/react-native-website.git",
|
|
99
|
+
contentPath: isLatest ? "docs" : `website/versioned_docs/version-${version}`
|
|
100
|
+
},
|
|
101
|
+
search: { ...DEFAULT_SEARCH },
|
|
102
|
+
server: {
|
|
103
|
+
name: "react-native-docs-mcp",
|
|
104
|
+
version: "0.2.0"
|
|
105
|
+
},
|
|
106
|
+
sections: ["the-new-architecture", "legacy", "releases"],
|
|
107
|
+
resourceUriScheme: "react-native-docs",
|
|
108
|
+
docsLabel: "React Native",
|
|
109
|
+
searchToolName: "search_react_native_docs",
|
|
110
|
+
searchToolDescription: "Search across React Native documentation. Returns relevant documentation pages with snippets.",
|
|
111
|
+
pathExample: "the-new-architecture/using-codegen",
|
|
112
|
+
docUrl: {
|
|
113
|
+
base: isLatest ? "https://reactnative.dev/docs" : `https://reactnative.dev/docs/${version}`,
|
|
114
|
+
// Frontmatter-id routing is a property of the Docusaurus content format,
|
|
115
|
+
// not of the version — versioned snapshots carry the same id overrides
|
|
116
|
+
useFrontmatterId: true
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
87
120
|
|
|
88
121
|
// ../../src/server.ts
|
|
89
122
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -118,12 +151,19 @@ var DocsManager = class {
|
|
|
118
151
|
async initialize() {
|
|
119
152
|
const repoExists = await this.checkRepoExists();
|
|
120
153
|
if (!repoExists) {
|
|
121
|
-
console.log("Cloning
|
|
154
|
+
console.log("Cloning documentation repository...");
|
|
122
155
|
await this.cloneRepo();
|
|
123
156
|
console.log("Repository cloned successfully");
|
|
124
157
|
} else {
|
|
125
158
|
console.log("Repository already exists");
|
|
126
159
|
}
|
|
160
|
+
try {
|
|
161
|
+
await fs.access(this.contentPath);
|
|
162
|
+
} catch {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Docs content path not found: ${this.contentPath}. The cloned repository has no such folder \u2014 if you passed --docs-version, check that this docs version exists.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
127
167
|
}
|
|
128
168
|
/**
|
|
129
169
|
* Check if repository exists locally
|
|
@@ -137,16 +177,33 @@ var DocsManager = class {
|
|
|
137
177
|
}
|
|
138
178
|
}
|
|
139
179
|
/**
|
|
140
|
-
* Clone the repository
|
|
180
|
+
* Clone the repository.
|
|
181
|
+
* Clones into a temp directory and renames into place so that two
|
|
182
|
+
* processes starting on a fresh machine (e.g. two MCP servers sharing one
|
|
183
|
+
* cache) don't corrupt each other; the loser discards its redundant copy.
|
|
141
184
|
*/
|
|
142
185
|
async cloneRepo() {
|
|
186
|
+
const tempPath = `${this.repoPath}.cloning-${process.pid}`;
|
|
143
187
|
try {
|
|
144
188
|
await fs.mkdir(path.dirname(this.repoPath), { recursive: true });
|
|
145
|
-
await this.git.clone(activeConfig.repo.url,
|
|
189
|
+
await this.git.clone(activeConfig.repo.url, tempPath, {
|
|
146
190
|
"--depth": 1
|
|
147
191
|
// Shallow clone for faster download
|
|
148
192
|
});
|
|
193
|
+
try {
|
|
194
|
+
await fs.rename(tempPath, this.repoPath);
|
|
195
|
+
} catch (renameError) {
|
|
196
|
+
if (await this.checkRepoExists()) {
|
|
197
|
+
await fs.rm(tempPath, { recursive: true, force: true });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
throw renameError;
|
|
201
|
+
}
|
|
149
202
|
} catch (error) {
|
|
203
|
+
try {
|
|
204
|
+
await fs.rm(tempPath, { recursive: true, force: true });
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
150
207
|
throw new Error(
|
|
151
208
|
`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`
|
|
152
209
|
);
|
|
@@ -243,6 +300,25 @@ var DocsManager = class {
|
|
|
243
300
|
this.fileCache.set(cacheKey, files);
|
|
244
301
|
return files;
|
|
245
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Filter a list of section names down to those whose directory actually
|
|
305
|
+
* exists under the content root. Lets the server avoid advertising
|
|
306
|
+
* sections that are empty in the checked-out docs (e.g. a versioned
|
|
307
|
+
* snapshot that predates a section).
|
|
308
|
+
*/
|
|
309
|
+
async getExistingSections(sections) {
|
|
310
|
+
const checks = await Promise.all(
|
|
311
|
+
sections.map(async (section) => {
|
|
312
|
+
try {
|
|
313
|
+
await fs.access(path.join(this.contentPath, section));
|
|
314
|
+
return section;
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
return checks.filter((section) => section !== null);
|
|
321
|
+
}
|
|
246
322
|
/**
|
|
247
323
|
* Read file content
|
|
248
324
|
* @param relativePath - Path relative to content root
|
|
@@ -309,11 +385,14 @@ function extractSection(path2) {
|
|
|
309
385
|
function normalizePath(filePath) {
|
|
310
386
|
return filePath.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.mdx?$/, "");
|
|
311
387
|
}
|
|
388
|
+
function titleCase(slug) {
|
|
389
|
+
return slug.replace(/[-_]/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
390
|
+
}
|
|
312
391
|
function extractTitleFromPath(filePath) {
|
|
313
392
|
const normalized = normalizePath(filePath);
|
|
314
393
|
const parts = normalized.split("/");
|
|
315
394
|
const filename = parts[parts.length - 1] || "Untitled";
|
|
316
|
-
return filename
|
|
395
|
+
return titleCase(filename);
|
|
317
396
|
}
|
|
318
397
|
|
|
319
398
|
// ../../src/embeddingService.ts
|
|
@@ -415,6 +494,17 @@ var EmbeddingService = class {
|
|
|
415
494
|
};
|
|
416
495
|
|
|
417
496
|
// ../../src/searchEngine.ts
|
|
497
|
+
function resolveDocSlug(path2, id) {
|
|
498
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
499
|
+
return path2;
|
|
500
|
+
}
|
|
501
|
+
if (id.includes("/")) {
|
|
502
|
+
return id;
|
|
503
|
+
}
|
|
504
|
+
const parts = path2.split("/");
|
|
505
|
+
parts[parts.length - 1] = id;
|
|
506
|
+
return parts.join("/");
|
|
507
|
+
}
|
|
418
508
|
var SearchEngine = class {
|
|
419
509
|
docsManager;
|
|
420
510
|
embeddingService;
|
|
@@ -441,12 +531,16 @@ var SearchEngine = class {
|
|
|
441
531
|
try {
|
|
442
532
|
const content = await this.docsManager.readDoc(docPath);
|
|
443
533
|
const parsedDoc = await parseMarkdown(content, docPath);
|
|
534
|
+
if (activeConfig.docUrl.useFrontmatterId) {
|
|
535
|
+
parsedDoc.path = resolveDocSlug(parsedDoc.path, parsedDoc.metadata.id);
|
|
536
|
+
}
|
|
444
537
|
this.documentIndex.set(parsedDoc.path, parsedDoc);
|
|
445
538
|
} catch (error) {
|
|
446
539
|
console.warn(`Failed to index document ${docPath}:`, error);
|
|
447
540
|
}
|
|
448
541
|
}
|
|
449
542
|
this.indexed = true;
|
|
543
|
+
this.embeddingsGenerated = false;
|
|
450
544
|
console.log(`Indexed ${this.documentIndex.size} documents`);
|
|
451
545
|
}
|
|
452
546
|
/**
|
|
@@ -615,14 +709,7 @@ var SearchEngine = class {
|
|
|
615
709
|
if (!this.indexed) {
|
|
616
710
|
await this.indexDocuments();
|
|
617
711
|
}
|
|
618
|
-
|
|
619
|
-
return this.documentIndex.get(normalizedPath) || null;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* List all available sections
|
|
623
|
-
*/
|
|
624
|
-
getSections() {
|
|
625
|
-
return [...activeConfig.sections];
|
|
712
|
+
return this.documentIndex.get(normalizePath(path2)) || null;
|
|
626
713
|
}
|
|
627
714
|
/**
|
|
628
715
|
* Get all documents in a section
|
|
@@ -690,22 +777,11 @@ function extractStructure(content) {
|
|
|
690
777
|
}
|
|
691
778
|
|
|
692
779
|
// ../../src/server.ts
|
|
693
|
-
function
|
|
694
|
-
return
|
|
780
|
+
function escapeRegExp(literal) {
|
|
781
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
695
782
|
}
|
|
696
783
|
function buildDocUrl(doc) {
|
|
697
|
-
|
|
698
|
-
const id = doc.metadata.id;
|
|
699
|
-
if (activeConfig.docUrl.useFrontmatterId && id) {
|
|
700
|
-
if (id.includes("/")) {
|
|
701
|
-
slug = id;
|
|
702
|
-
} else {
|
|
703
|
-
const parts = doc.path.split("/");
|
|
704
|
-
parts[parts.length - 1] = id;
|
|
705
|
-
slug = parts.join("/");
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return `${activeConfig.docUrl.base}/${slug}`;
|
|
784
|
+
return `${activeConfig.docUrl.base}/${doc.path}`;
|
|
709
785
|
}
|
|
710
786
|
async function createServer() {
|
|
711
787
|
const docsManager = new DocsManager();
|
|
@@ -724,16 +800,20 @@ async function createServer() {
|
|
|
724
800
|
);
|
|
725
801
|
const searchDocsSchema = z.object({
|
|
726
802
|
query: z.string().describe("Search query string"),
|
|
727
|
-
section: z.string().optional().
|
|
803
|
+
section: z.string().optional().refine((section) => section === void 0 || activeConfig.sections.includes(section), {
|
|
804
|
+
message: `Unknown section. Valid sections: ${activeConfig.sections.join(", ")}`
|
|
805
|
+
}).describe(`Filter by section (${activeConfig.sections.join(", ")})`),
|
|
728
806
|
limit: z.number().min(1).max(activeConfig.search.maxLimit).optional().describe("Maximum number of results")
|
|
729
807
|
});
|
|
730
808
|
const getDocSchema = z.object({
|
|
731
|
-
path: z.string().describe(
|
|
809
|
+
path: z.string().describe(`Document path (e.g., "${activeConfig.pathExample}")`),
|
|
810
|
+
full: z.boolean().optional().describe("Return the full raw page content instead of a ~1500 char summary")
|
|
732
811
|
});
|
|
733
|
-
const resourceUriRegex = new RegExp(`^${activeConfig.resourceUriScheme}:\\/\\/(.+)$`);
|
|
812
|
+
const resourceUriRegex = new RegExp(`^${escapeRegExp(activeConfig.resourceUriScheme)}:\\/\\/(.+)$`);
|
|
734
813
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
814
|
+
const existingSections = await docsManager.getExistingSections(activeConfig.sections);
|
|
735
815
|
return {
|
|
736
|
-
resources:
|
|
816
|
+
resources: existingSections.map((section) => {
|
|
737
817
|
const override = activeConfig.sectionResourceOverrides?.[section];
|
|
738
818
|
return {
|
|
739
819
|
uri: `${activeConfig.resourceUriScheme}://${section}`,
|
|
@@ -822,13 +902,17 @@ ${doc.content}`
|
|
|
822
902
|
},
|
|
823
903
|
{
|
|
824
904
|
name: "get_doc",
|
|
825
|
-
description: `Get a concise summary of a documentation page (~1500 chars). Use ${activeConfig.searchToolName} first - only call this if you need more detail than the search snippet provides.`,
|
|
905
|
+
description: `Get a concise summary of a documentation page (~1500 chars), or the full raw page with full:true. Use ${activeConfig.searchToolName} first - only call this if you need more detail than the search snippet provides.`,
|
|
826
906
|
inputSchema: {
|
|
827
907
|
type: "object",
|
|
828
908
|
properties: {
|
|
829
909
|
path: {
|
|
830
910
|
type: "string",
|
|
831
|
-
description:
|
|
911
|
+
description: `Document path (e.g., "${activeConfig.pathExample}")`
|
|
912
|
+
},
|
|
913
|
+
full: {
|
|
914
|
+
type: "boolean",
|
|
915
|
+
description: "Return the full raw page content instead of a ~1500 char summary"
|
|
832
916
|
}
|
|
833
917
|
},
|
|
834
918
|
required: ["path"]
|
|
@@ -872,7 +956,7 @@ ${doc.content}`
|
|
|
872
956
|
};
|
|
873
957
|
}
|
|
874
958
|
case "list_sections": {
|
|
875
|
-
const sections =
|
|
959
|
+
const sections = await docsManager.getExistingSections(activeConfig.sections);
|
|
876
960
|
return {
|
|
877
961
|
content: [
|
|
878
962
|
{
|
|
@@ -883,13 +967,16 @@ ${doc.content}`
|
|
|
883
967
|
};
|
|
884
968
|
}
|
|
885
969
|
case "get_doc": {
|
|
886
|
-
const { path: path2 } = getDocSchema.parse(args);
|
|
970
|
+
const { path: path2, full } = getDocSchema.parse(args);
|
|
887
971
|
const doc = await searchEngine.getDocByPath(path2);
|
|
888
972
|
if (!doc) {
|
|
889
973
|
throw new Error(`Document not found: ${path2}`);
|
|
890
974
|
}
|
|
891
|
-
const
|
|
892
|
-
|
|
975
|
+
const body = full ? { content: doc.content, note: "Full page content." } : {
|
|
976
|
+
summary: summarizeContent(doc.content, 1500),
|
|
977
|
+
structure: extractStructure(doc.content),
|
|
978
|
+
note: "This is a summary. Pass full:true for the complete page, or visit the URL."
|
|
979
|
+
};
|
|
893
980
|
return {
|
|
894
981
|
content: [
|
|
895
982
|
{
|
|
@@ -900,10 +987,8 @@ ${doc.content}`
|
|
|
900
987
|
section: doc.section,
|
|
901
988
|
title: doc.metadata.title,
|
|
902
989
|
description: doc.metadata.description,
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
url: buildDocUrl(doc),
|
|
906
|
-
note: "This is a summary. Visit the URL for full documentation."
|
|
990
|
+
...body,
|
|
991
|
+
url: buildDocUrl(doc)
|
|
907
992
|
},
|
|
908
993
|
null,
|
|
909
994
|
2
|
|
@@ -951,7 +1036,33 @@ ${doc.content}`
|
|
|
951
1036
|
}
|
|
952
1037
|
|
|
953
1038
|
// src/index.ts
|
|
954
|
-
|
|
1039
|
+
var argv = process.argv.slice(2);
|
|
1040
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
1041
|
+
console.log(resolveReactNativeDocsPreset().server.version);
|
|
1042
|
+
process.exit(0);
|
|
1043
|
+
}
|
|
1044
|
+
function parseDocsVersionArg(args) {
|
|
1045
|
+
for (const arg of args) {
|
|
1046
|
+
const match = arg.match(/^--docs-version=(.+)$/);
|
|
1047
|
+
if (match) return match[1];
|
|
1048
|
+
}
|
|
1049
|
+
const flagIndex = args.indexOf("--docs-version");
|
|
1050
|
+
const value = flagIndex !== -1 ? args[flagIndex + 1] : void 0;
|
|
1051
|
+
if (value && !value.startsWith("-")) {
|
|
1052
|
+
return value;
|
|
1053
|
+
}
|
|
1054
|
+
return void 0;
|
|
1055
|
+
}
|
|
1056
|
+
var docsVersion = parseDocsVersionArg(argv) ?? process.env.REACT_NATIVE_DOCS_VERSION ?? LATEST_VERSION;
|
|
1057
|
+
if (docsVersion !== LATEST_VERSION) {
|
|
1058
|
+
console.error(`Using React Native docs version: ${docsVersion} (best-effort; only "latest" is fully verified)`);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
configure(resolveReactNativeDocsPreset(docsVersion));
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1064
|
+
process.exit(1);
|
|
1065
|
+
}
|
|
955
1066
|
createServer().catch((error) => {
|
|
956
1067
|
console.error("Failed to start server:", error);
|
|
957
1068
|
process.exit(1);
|