@react-aria/mcp 3.0.0-nightly-46eb0efb5-251114
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 +138 -0
- package/dist/react-aria/src/index.js +19 -0
- package/dist/shared/src/page-manager.js +83 -0
- package/dist/shared/src/parser.js +61 -0
- package/dist/shared/src/server.js +76 -0
- package/dist/shared/src/types.js +1 -0
- package/dist/shared/src/utils.js +30 -0
- package/package.json +41 -0
- package/src/index.ts +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @react-aria/mcp
|
|
2
|
+
|
|
3
|
+
The `@react-aria/mcp` package provides a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) server for React Aria documentation. It exposes a set of tools that MCP clients can discover and call to browse the docs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Quick Start
|
|
8
|
+
|
|
9
|
+
Simply run the server using npx:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @react-aria/mcp@latest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Using with an MCP client
|
|
16
|
+
|
|
17
|
+
Add the server to your MCP client configuration (the exact file and schema may depend on your client).
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"React Aria": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["@react-aria/mcp@latest"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>Cursor</summary>
|
|
32
|
+
|
|
33
|
+
#### Click the button to install:
|
|
34
|
+
|
|
35
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=React%20Aria&config=eyJjb21tYW5kIjoibnB4IEByZWFjdC1hcmlhL21jcEBsYXRlc3QifQ%3D%3D)
|
|
36
|
+
|
|
37
|
+
Or follow the MCP install [guide](https://docs.cursor.com/en/context/mcp#installing-mcp-servers) and use the standard config above.
|
|
38
|
+
|
|
39
|
+
</details>
|
|
40
|
+
|
|
41
|
+
<details>
|
|
42
|
+
<summary>VS Code</summary>
|
|
43
|
+
|
|
44
|
+
#### Click the button to install:
|
|
45
|
+
|
|
46
|
+
[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](vscode:mcp/install?%7B%22name%22%3A%22React%20Aria%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22%40react-aria%2Fmcp%40latest%22%5D%7D)
|
|
47
|
+
|
|
48
|
+
#### Or install manually:
|
|
49
|
+
|
|
50
|
+
Follow the MCP install [guide](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server) and use the standard config above. You can also add the server using the VS Code CLI:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
code --add-mcp '{"name":"React Aria","command":"npx","args":["@react-aria/mcp@latest"]}'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
<details>
|
|
59
|
+
<summary>Claude Code</summary>
|
|
60
|
+
|
|
61
|
+
Use the Claude Code CLI to add the server:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
claude mcp add react-aria npx @react-aria/mcp@latest
|
|
65
|
+
```
|
|
66
|
+
For more information, see the [Claude Code MCP documentation](https://docs.claude.com/en/docs/claude-code/mcp).
|
|
67
|
+
</details>
|
|
68
|
+
|
|
69
|
+
<details>
|
|
70
|
+
<summary>Codex</summary>
|
|
71
|
+
|
|
72
|
+
Create or edit the configuration file `~/.codex/config.toml` and add:
|
|
73
|
+
|
|
74
|
+
```toml
|
|
75
|
+
[mcp_servers.react-aria]
|
|
76
|
+
command = "npx"
|
|
77
|
+
args = ["@react-aria/mcp@latest"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For more information, see the [Codex MCP documentation](https://github.com/openai/codex/blob/main/docs/config.md#mcp_servers).
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
<details>
|
|
85
|
+
<summary>Gemini CLI</summary>
|
|
86
|
+
|
|
87
|
+
Use the Gemini CLI to add the server:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
gemini mcp add react-aria npx @react-aria/mcp@latest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For more information, see the [Gemini CLI MCP documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#how-to-set-up-your-mcp-server).
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>Windsurf</summary>
|
|
99
|
+
|
|
100
|
+
Follow Windsurf MCP [documentation](https://docs.windsurf.com/windsurf/cascade/mcp) and use the standard config above.
|
|
101
|
+
|
|
102
|
+
</details>
|
|
103
|
+
|
|
104
|
+
## Tools
|
|
105
|
+
|
|
106
|
+
| Tool | Input | Description |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `list_react_aria_pages` | `{ includeDescription?: boolean }` | List available pages in the React Aria docs. |
|
|
109
|
+
| `get_react_aria_page_info` | `{ page_name: string }` | Return page description and list of section titles. |
|
|
110
|
+
| `get_react_aria_page` | `{ page_name: string, section_name?: string }` | Return full page markdown, or only the specified section. |
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
### Testing locally
|
|
115
|
+
|
|
116
|
+
Build the docs and MCP server locally, then start the docs server.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
yarn workspace @react-spectrum/s2-docs generate:md
|
|
120
|
+
yarn workspace @react-aria/mcp build
|
|
121
|
+
yarn start:s2-docs
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Update your MCP client configuration to use the local MCP server:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"React Aria": {
|
|
130
|
+
"command": "node",
|
|
131
|
+
"args": ["{your path here}/react-spectrum/packages/dev/mcp/react-aria/dist/index.js"],
|
|
132
|
+
"env": {
|
|
133
|
+
"DOCS_CDN_BASE": "http://localhost:1234"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { errorToString } from '../../shared/src/utils.js';
|
|
4
|
+
import { startServer } from '../../shared/src/server.js';
|
|
5
|
+
// CLI entry for React Aria
|
|
6
|
+
(async () => {
|
|
7
|
+
try {
|
|
8
|
+
const arg = (process.argv[2] || '').trim();
|
|
9
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
10
|
+
console.log('Usage: npx @react-aria/mcp@latest\n\nStarts the MCP server for React Aria documentation.');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
await startServer('react-aria', '0.1.0');
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.error(errorToString(err));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { DEFAULT_CDN_BASE, fetchText } from './utils.js';
|
|
2
|
+
import { extractNameAndDescription, parseSectionsFromMarkdown } from './parser.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
// Cache of parsed pages
|
|
5
|
+
const pageCache = new Map();
|
|
6
|
+
// Whether we've loaded the page index for a library yet.
|
|
7
|
+
const pageIndexLoaded = new Set();
|
|
8
|
+
function libBaseUrl(library) {
|
|
9
|
+
return `${DEFAULT_CDN_BASE}/${library}`;
|
|
10
|
+
}
|
|
11
|
+
// Build an index of pages for the given library from the CDN's llms.txt.
|
|
12
|
+
export async function buildPageIndex(library) {
|
|
13
|
+
if (pageIndexLoaded.has(library)) {
|
|
14
|
+
return Array.from(pageCache.values()).filter(p => p.key.startsWith(`${library}/`));
|
|
15
|
+
}
|
|
16
|
+
const pages = [];
|
|
17
|
+
// Read llms.txt to enumerate available pages without downloading them all.
|
|
18
|
+
const llmsUrl = `${libBaseUrl(library)}/llms.txt`;
|
|
19
|
+
const txt = await fetchText(llmsUrl);
|
|
20
|
+
const re = /^\s*-\s*\[([^\]]+)\]\(([^)]+)\)(?:\s*:\s*(.*))?\s*$/;
|
|
21
|
+
for (const line of txt.split(/\r?\n/)) {
|
|
22
|
+
const m = line.match(re);
|
|
23
|
+
if (!m) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const display = (m[1] || '').trim();
|
|
27
|
+
const href = (m[2] || '').trim();
|
|
28
|
+
const description = (m[3] || '').trim() || undefined;
|
|
29
|
+
if (!href || !/\.md$/i.test(href)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const key = href.replace(/\.md$/i, '').replace(/\\/g, '/');
|
|
33
|
+
const name = display || path.basename(key);
|
|
34
|
+
const filePath = `${DEFAULT_CDN_BASE}/${key}.md`;
|
|
35
|
+
const info = { key, name, description, filePath, sections: [] };
|
|
36
|
+
pages.push(info);
|
|
37
|
+
pageCache.set(info.key, info);
|
|
38
|
+
}
|
|
39
|
+
pageIndexLoaded.add(library);
|
|
40
|
+
return pages.sort((a, b) => a.key.localeCompare(b.key));
|
|
41
|
+
}
|
|
42
|
+
export async function ensureParsedPage(info) {
|
|
43
|
+
if (info.sections && info.sections.length > 0 && info.description !== undefined) {
|
|
44
|
+
return info;
|
|
45
|
+
}
|
|
46
|
+
const text = await fetchText(info.filePath);
|
|
47
|
+
const lines = text.split(/\r?\n/);
|
|
48
|
+
const { name, description } = extractNameAndDescription(lines);
|
|
49
|
+
const sections = parseSectionsFromMarkdown(lines);
|
|
50
|
+
const updated = { ...info, name: name || info.name, description, sections };
|
|
51
|
+
pageCache.set(updated.key, updated);
|
|
52
|
+
return updated;
|
|
53
|
+
}
|
|
54
|
+
export async function resolvePageRef(library, pageName) {
|
|
55
|
+
await buildPageIndex(library);
|
|
56
|
+
if (pageCache.has(pageName)) {
|
|
57
|
+
return pageCache.get(pageName);
|
|
58
|
+
}
|
|
59
|
+
if (pageName.includes('/')) {
|
|
60
|
+
const normalized = pageName.replace(/\\/g, '/');
|
|
61
|
+
const prefix = normalized.split('/', 1)[0];
|
|
62
|
+
if (prefix !== library) {
|
|
63
|
+
throw new Error(`Page '${pageName}' is not in the '${library}' library.`);
|
|
64
|
+
}
|
|
65
|
+
const maybe = pageCache.get(normalized);
|
|
66
|
+
if (maybe) {
|
|
67
|
+
return maybe;
|
|
68
|
+
}
|
|
69
|
+
const filePath = `${DEFAULT_CDN_BASE}/${normalized}.md`;
|
|
70
|
+
const stub = { key: normalized, name: path.basename(normalized), description: undefined, filePath, sections: [] };
|
|
71
|
+
pageCache.set(stub.key, stub);
|
|
72
|
+
return stub;
|
|
73
|
+
}
|
|
74
|
+
const key = `${library}/${pageName}`;
|
|
75
|
+
const maybe = pageCache.get(key);
|
|
76
|
+
if (maybe) {
|
|
77
|
+
return maybe;
|
|
78
|
+
}
|
|
79
|
+
const filePath = `${DEFAULT_CDN_BASE}/${key}.md`;
|
|
80
|
+
const stub = { key, name: pageName, description: undefined, filePath, sections: [] };
|
|
81
|
+
pageCache.set(stub.key, stub);
|
|
82
|
+
return stub;
|
|
83
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function parseSectionsFromMarkdown(lines) {
|
|
2
|
+
const sections = [];
|
|
3
|
+
let inCode = false;
|
|
4
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
5
|
+
const line = lines[idx];
|
|
6
|
+
if (/^```/.test(line.trim())) {
|
|
7
|
+
inCode = !inCode;
|
|
8
|
+
}
|
|
9
|
+
if (inCode) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (line.startsWith('## ')) {
|
|
13
|
+
const name = line.replace(/^##\s+/, '').trim();
|
|
14
|
+
sections.push({ name, startLine: idx, endLine: lines.length });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
for (let s = 0; s < sections.length - 1; s++) {
|
|
18
|
+
sections[s].endLine = sections[s + 1].startLine;
|
|
19
|
+
}
|
|
20
|
+
return sections;
|
|
21
|
+
}
|
|
22
|
+
export function extractNameAndDescription(lines) {
|
|
23
|
+
let name = '';
|
|
24
|
+
let description = undefined;
|
|
25
|
+
let i = 0;
|
|
26
|
+
for (; i < lines.length; i++) {
|
|
27
|
+
const line = lines[i];
|
|
28
|
+
if (line.startsWith('# ')) {
|
|
29
|
+
name = line.replace(/^#\s+/, '').trim();
|
|
30
|
+
i++;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let descLines = [];
|
|
35
|
+
let inCode = false;
|
|
36
|
+
for (; i < lines.length; i++) {
|
|
37
|
+
const line = lines[i];
|
|
38
|
+
if (/^```/.test(line.trim())) {
|
|
39
|
+
inCode = !inCode;
|
|
40
|
+
}
|
|
41
|
+
if (inCode) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (line.trim() === '') {
|
|
45
|
+
if (descLines.length > 0) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (/^#{1,6}\s/.test(line) || /^</.test(line.trim())) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
descLines.push(line);
|
|
56
|
+
}
|
|
57
|
+
if (descLines.length > 0) {
|
|
58
|
+
description = descLines.join('\n').trim();
|
|
59
|
+
}
|
|
60
|
+
return { name, description };
|
|
61
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { buildPageIndex, ensureParsedPage, resolvePageRef } from './page-manager.js';
|
|
2
|
+
import { errorToString, fetchText } from './utils.js';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { parseSectionsFromMarkdown } from './parser.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
export async function startServer(library, version, registerAdditionalTools) {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: library === 's2' ? 's2-docs-server' : 'react-aria-docs-server',
|
|
10
|
+
version
|
|
11
|
+
});
|
|
12
|
+
// Build page index at startup.
|
|
13
|
+
try {
|
|
14
|
+
await buildPageIndex(library);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
console.warn(`Warning: failed to load ${library} docs index (${errorToString(e)}).`);
|
|
18
|
+
}
|
|
19
|
+
const toolPrefix = library === 's2' ? 's2' : 'react_aria';
|
|
20
|
+
server.registerTool(`list_${toolPrefix}_pages`, {
|
|
21
|
+
title: library === 's2' ? 'List React Spectrum (@react-spectrum/s2) docs pages' : 'List React Aria docs pages',
|
|
22
|
+
description: `Returns a list of available pages in the ${library} docs.`,
|
|
23
|
+
inputSchema: { includeDescription: z.boolean().optional() }
|
|
24
|
+
}, async ({ includeDescription }) => {
|
|
25
|
+
const pages = await buildPageIndex(library);
|
|
26
|
+
const items = pages
|
|
27
|
+
.sort((a, b) => a.key.localeCompare(b.key))
|
|
28
|
+
.map(p => includeDescription ? { name: p.name, description: p.description ?? '' } : { name: p.name });
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: 'text', text: JSON.stringify(items, null, 2) }]
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
server.registerTool(`get_${toolPrefix}_page_info`, {
|
|
34
|
+
title: 'Get page info',
|
|
35
|
+
description: 'Returns page description and list of sections for a given page.',
|
|
36
|
+
inputSchema: { page_name: z.string() }
|
|
37
|
+
}, async ({ page_name }) => {
|
|
38
|
+
const ref = await resolvePageRef(library, page_name);
|
|
39
|
+
const info = await ensureParsedPage(ref);
|
|
40
|
+
const out = {
|
|
41
|
+
name: info.name,
|
|
42
|
+
description: info.description ?? '',
|
|
43
|
+
sections: info.sections.map(s => s.name)
|
|
44
|
+
};
|
|
45
|
+
return { content: [{ type: 'text', text: JSON.stringify(out, null, 2) }] };
|
|
46
|
+
});
|
|
47
|
+
server.registerTool(`get_${toolPrefix}_page`, {
|
|
48
|
+
title: 'Get page markdown',
|
|
49
|
+
description: 'Returns the full markdown content for a page, or a specific section if provided.',
|
|
50
|
+
inputSchema: { page_name: z.string(), section_name: z.string().optional() }
|
|
51
|
+
}, async ({ page_name, section_name }) => {
|
|
52
|
+
const ref = await resolvePageRef(library, page_name);
|
|
53
|
+
let text;
|
|
54
|
+
text = await fetchText(ref.filePath);
|
|
55
|
+
if (!section_name) {
|
|
56
|
+
return { content: [{ type: 'text', text }] };
|
|
57
|
+
}
|
|
58
|
+
const lines = text.split(/\r?\n/);
|
|
59
|
+
const sections = parseSectionsFromMarkdown(lines);
|
|
60
|
+
let section = sections.find(s => s.name === section_name);
|
|
61
|
+
if (!section) {
|
|
62
|
+
section = sections.find(s => s.name.toLowerCase() === section_name.toLowerCase());
|
|
63
|
+
}
|
|
64
|
+
if (!section) {
|
|
65
|
+
const available = sections.map(s => s.name).join(', ');
|
|
66
|
+
throw new Error(`Section '${section_name}' not found in ${ref.key}. Available: ${available}`);
|
|
67
|
+
}
|
|
68
|
+
const snippet = lines.slice(section.startLine, section.endLine).join('\n');
|
|
69
|
+
return { content: [{ type: 'text', text: snippet }] };
|
|
70
|
+
});
|
|
71
|
+
if (registerAdditionalTools) {
|
|
72
|
+
await registerAdditionalTools(server);
|
|
73
|
+
}
|
|
74
|
+
const transport = new StdioServerTransport();
|
|
75
|
+
await server.connect(transport);
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function errorToString(err) {
|
|
2
|
+
if (err && typeof err === 'object' && 'stack' in err && typeof err.stack === 'string') {
|
|
3
|
+
return err.stack;
|
|
4
|
+
}
|
|
5
|
+
if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') {
|
|
6
|
+
return err.message;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return JSON.stringify(err);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return String(err);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// CDN base for docs. Can be overridden via env variable.
|
|
16
|
+
export const DEFAULT_CDN_BASE = process.env.DOCS_CDN_BASE ?? 'https://react-spectrum.adobe.com/beta';
|
|
17
|
+
export async function fetchText(url, timeoutMs = 15000) {
|
|
18
|
+
const ctrl = new AbortController();
|
|
19
|
+
const id = setTimeout(() => ctrl.abort(), timeoutMs).unref?.();
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(url, { signal: ctrl.signal, cache: 'no-store' });
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
throw new Error(`HTTP ${res.status} for ${url}`);
|
|
24
|
+
}
|
|
25
|
+
return await res.text();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
clearTimeout(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-aria/mcp",
|
|
3
|
+
"version": "3.0.0-nightly-46eb0efb5-251114",
|
|
4
|
+
"description": "MCP server for React Aria documentation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "dist/react-aria/src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"prepublishOnly": "yarn build",
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"start": "node dist/react-aria/src/index.js",
|
|
11
|
+
"dev": "node --enable-source-maps dist/react-aria/src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.17.3",
|
|
15
|
+
"@swc/helpers": "^0.5.0",
|
|
16
|
+
"zod": "^3.23.8"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.8.2"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/adobe/react-spectrum"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"src"
|
|
35
|
+
],
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"main": "dist/main.js",
|
|
38
|
+
"module": "dist/module.js",
|
|
39
|
+
"types": "dist/types.d.ts",
|
|
40
|
+
"source": "src/index.ts"
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import {errorToString} from '../../shared/src/utils.js';
|
|
4
|
+
import {startServer} from '../../shared/src/server.js';
|
|
5
|
+
|
|
6
|
+
// CLI entry for React Aria
|
|
7
|
+
(async () => {
|
|
8
|
+
try {
|
|
9
|
+
const arg = (process.argv[2] || '').trim();
|
|
10
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
11
|
+
console.log('Usage: npx @react-aria/mcp@latest\n\nStarts the MCP server for React Aria documentation.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
await startServer('react-aria', '0.1.0');
|
|
15
|
+
} catch (err) {
|
|
16
|
+
console.error(errorToString(err));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
})();
|