Skip to content

Commit 49bb12c

Browse files
authored
fix: mcp improvements (#9034)
* remove id and rename title -> description * include library name in tool names * update docs base url * cleanup readme * readme
1 parent 59a2b92 commit 49bb12c

File tree

2 files changed

+73
-28
lines changed

2 files changed

+73
-28
lines changed

packages/dev/mcp/README.md

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,55 @@ Follow Windsurf MCP [documentation](https://docs.windsurf.com/windsurf/cascade/m
118118

119119
## Tools
120120

121+
### React Spectrum (S2)
122+
123+
| Tool | Input | Description |
124+
| --- | --- | --- |
125+
| `list_s2_pages` | `{ includeDescription?: boolean }` | List available pages in the S2 docs. |
126+
| `get_s2_page_info` | `{ page_name: string }` | Return page description and list of section titles. |
127+
| `get_s2_page` | `{ page_name: string, section_name?: string }` | Return full page markdown, or only the specified section. |
128+
| `search_s2_icons` | `{ terms: string or string[] }` | Search S2 workflow icon names. |
129+
| `search_s2_illustrations` | `{ terms: string or string[] }` | Search S2 illustration names. |
130+
131+
### React Aria
132+
121133
| Tool | Input | Description |
122134
| --- | --- | --- |
123-
| `list_pages` | `{ includeDescription?: boolean }` | List available pages in the selected docs library. |
124-
| `get_page_info` | `{ page_name: string }` | Return page description and list of section titles. |
125-
| `get_page` | `{ page_name: string, section_name?: string }` | Return full page markdown, or only the specified section. |
126-
| `search_icons` (S2 only) | `{ terms: string or string[] }` | Search S2 workflow icon names. |
127-
| `search_illustrations` (S2 only) | `{ terms: string or string[] }` | Search S2 illustration names. |
135+
| `list_react_aria_pages` | `{ includeDescription?: boolean }` | List available pages in the React Aria docs. |
136+
| `get_react_aria_page_info` | `{ page_name: string }` | Return page description and list of section titles. |
137+
| `get_react_aria_page` | `{ page_name: string, section_name?: string }` | Return full page markdown, or only the specified section. |
138+
139+
## Development
140+
141+
### Testing locally
142+
143+
Build the docs and MCP server locally, then start the docs server.
144+
145+
```bash
146+
yarn workspace @react-spectrum/s2-docs generate:md
147+
yarn workspace @react-spectrum/mcp build
148+
yarn start:s2-docs
149+
```
150+
151+
Update your MCP client configuration to use the local MCP server:
152+
153+
```json
154+
{
155+
"mcpServers": {
156+
"React Spectrum (S2)": {
157+
"command": "node",
158+
"args": ["{your path here}/react-spectrum/packages/dev/mcp/dist/index.js", "s2"],
159+
"env": {
160+
"DOCS_CDN_BASE": "http://localhost:1234"
161+
}
162+
},
163+
"React Aria": {
164+
"command": "node",
165+
"args": ["{your path here}/react-spectrum/packages/dev/mcp/dist/index.js", "react-aria"],
166+
"env": {
167+
"DOCS_CDN_BASE": "http://localhost:1234"
168+
}
169+
}
170+
}
171+
}
172+
```

packages/dev/mcp/src/index.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ type SectionInfo = {
1515

1616
type PageInfo = {
1717
key: string, // e.g. "s2/Button"
18-
title: string, // from top-level heading
19-
description?: string, // first paragraph after title
18+
name: string, // from top-level heading
19+
description?: string, // first paragraph after name
2020
filePath: string, // absolute path to markdown file
2121
sections: SectionInfo[]
2222
};
@@ -42,7 +42,7 @@ const __dirname = path.dirname(__filename);
4242

4343
// CDN base for docs. Can be overridden via env variable.
4444
const DEFAULT_CDN_BASE = process.env.DOCS_CDN_BASE
45-
?? 'https://reactspectrum.blob.core.windows.net/reactspectrum/7d2883a56fb1a0554864b21324d405f758deb3ce/s2-docs';
45+
?? 'https://reactspectrum.blob.core.windows.net/reactspectrum/a22a0aed3e97d0a23b9883679798b85eed68413d/s2-docs';
4646

4747
function libBaseUrl(library: Library) {
4848
return `${DEFAULT_CDN_BASE}/${library}`;
@@ -126,12 +126,12 @@ async function buildPageIndex(library: Library): Promise<PageInfo[]> {
126126
if (!m) {continue;}
127127
const display = (m[1] || '').trim();
128128
const href = (m[2] || '').trim();
129-
const desc = (m[3] || '').trim() || undefined;
129+
const description = (m[3] || '').trim() || undefined;
130130
if (!href || !/\.md$/i.test(href)) {continue;}
131131
const key = href.replace(/\.md$/i, '').replace(/\\/g, '/');
132-
const title = display || path.basename(key);
133-
const url = `${DEFAULT_CDN_BASE}/${key}.md`;
134-
const info: PageInfo = {key, title, description: desc, filePath: url, sections: []};
132+
const name = display || path.basename(key);
133+
const filePath = `${DEFAULT_CDN_BASE}/${key}.md`;
134+
const info: PageInfo = {key, name, description, filePath, sections: []};
135135
pages.push(info);
136136
pageCache.set(info.key, info);
137137
}
@@ -158,15 +158,15 @@ function parseSectionsFromMarkdown(lines: string[]): SectionInfo[] {
158158
return sections;
159159
}
160160

161-
function extractTitleAndDescription(lines: string[]): {title: string, description?: string} {
162-
let title = '';
161+
function extractNameAndDescription(lines: string[]): {name: string, description?: string} {
162+
let name = '';
163163
let description: string | undefined = undefined;
164164

165165
let i = 0;
166166
for (; i < lines.length; i++) {
167167
const line = lines[i];
168168
if (line.startsWith('# ')) {
169-
title = line.replace(/^#\s+/, '').trim();
169+
name = line.replace(/^#\s+/, '').trim();
170170
i++;
171171
break;
172172
}
@@ -188,7 +188,7 @@ function extractTitleAndDescription(lines: string[]): {title: string, descriptio
188188
description = descLines.join('\n').trim();
189189
}
190190

191-
return {title, description};
191+
return {name, description};
192192
}
193193

194194
async function ensureParsedPage(info: PageInfo): Promise<PageInfo> {
@@ -198,9 +198,9 @@ async function ensureParsedPage(info: PageInfo): Promise<PageInfo> {
198198

199199
const text = await fetchText(info.filePath);
200200
const lines = text.split(/\r?\n/);
201-
const {title, description} = extractTitleAndDescription(lines);
201+
const {name, description} = extractNameAndDescription(lines);
202202
const sections = parseSectionsFromMarkdown(lines);
203-
const updated = {...info, title: title || info.title, description, sections};
203+
const updated = {...info, name: name || info.name, description, sections};
204204
pageCache.set(updated.key, updated);
205205
return updated;
206206
}
@@ -222,7 +222,7 @@ async function resolvePageRef(library: Library, pageName: string): Promise<PageI
222222
const maybe = pageCache.get(normalized);
223223
if (maybe) {return maybe;}
224224
const filePath = `${DEFAULT_CDN_BASE}/${normalized}.md`;
225-
const stub: PageInfo = {key: normalized, title: path.basename(normalized), description: undefined, filePath, sections: []};
225+
const stub: PageInfo = {key: normalized, name: path.basename(normalized), description: undefined, filePath, sections: []};
226226
pageCache.set(stub.key, stub);
227227
return stub;
228228
}
@@ -231,7 +231,7 @@ async function resolvePageRef(library: Library, pageName: string): Promise<PageI
231231
const maybe = pageCache.get(key);
232232
if (maybe) {return maybe;}
233233
const filePath = `${DEFAULT_CDN_BASE}/${key}.md`;
234-
const stub: PageInfo = {key, title: pageName, description: undefined, filePath, sections: []};
234+
const stub: PageInfo = {key, name: pageName, description: undefined, filePath, sections: []};
235235
pageCache.set(stub.key, stub);
236236
return stub;
237237
}
@@ -250,8 +250,9 @@ async function startServer(library: Library) {
250250
}
251251
252252
// list_pages tool
253+
const toolPrefix = library === 's2' ? 's2' : 'react_aria';
253254
server.registerTool(
254-
'list_pages',
255+
`list_${toolPrefix}_pages`,
255256
{
256257
title: library === 's2' ? 'List React Spectrum (@react-spectrum/s2) docs pages' : 'List React Aria docs pages',
257258
description: `Returns a list of available pages in the ${library} docs.`,
@@ -261,7 +262,7 @@ async function startServer(library: Library) {
261262
const pages = await buildPageIndex(library);
262263
const items = pages
263264
.sort((a, b) => a.key.localeCompare(b.key))
264-
.map(p => includeDescription ? {key: p.key, title: p.title, description: p.description ?? ''} : {key: p.key, title: p.title});
265+
.map(p => includeDescription ? {name: p.name, description: p.description ?? ''} : {name: p.name});
265266
return {
266267
content: [{type: 'text', text: JSON.stringify(items, null, 2)}]
267268
};
@@ -270,7 +271,7 @@ async function startServer(library: Library) {
270271
271272
// get_page_info tool
272273
server.registerTool(
273-
'get_page_info',
274+
`get_${toolPrefix}_page_info`,
274275
{
275276
title: 'Get page info',
276277
description: 'Returns page description and list of sections for a given page.',
@@ -280,8 +281,7 @@ async function startServer(library: Library) {
280281
const ref = await resolvePageRef(library, page_name);
281282
const info = await ensureParsedPage(ref);
282283
const out = {
283-
key: info.key,
284-
title: info.title,
284+
name: info.name,
285285
description: info.description ?? '',
286286
sections: info.sections.map(s => s.name)
287287
};
@@ -291,7 +291,7 @@ async function startServer(library: Library) {
291291
292292
// get_page tool
293293
server.registerTool(
294-
'get_page',
294+
`get_${toolPrefix}_page`,
295295
{
296296
title: 'Get page markdown',
297297
description: 'Returns the full markdown content for a page, or a specific section if provided.',
@@ -324,7 +324,7 @@ async function startServer(library: Library) {
324324
if (library === 's2') {
325325
// search_icons tool
326326
server.registerTool(
327-
'search_icons',
327+
'search_s2_icons',
328328
{
329329
title: 'Search S2 icons',
330330
description: 'Searches the S2 workflow icon set by one or more terms; returns matching icon names.',
@@ -361,7 +361,7 @@ async function startServer(library: Library) {
361361

362362
// search_illustrations tool
363363
server.registerTool(
364-
'search_illustrations',
364+
'search_s2_illustrations',
365365
{
366366
title: 'Search S2 illustrations',
367367
description: 'Searches the S2 illustrations set by one or more terms; returns matching illustration names.',

0 commit comments

Comments
 (0)