Skip to content

Commit 9b83d70

Browse files
committed
WIP
1 parent 31305b6 commit 9b83d70

File tree

3 files changed

+117
-13
lines changed

3 files changed

+117
-13
lines changed

docs/.vitepress/client.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,19 @@ async function main() {
4545
// List resources
4646
const resources = await client.listResources()
4747
console.log('list resources', resources)
48+
const resourceTemplates = await client.listResourceTemplates()
49+
console.log('list resource templates', resourceTemplates)
4850
const contents = await client.readResource({
49-
uri: 'vue-i18n://contents'
51+
uri: 'vue-i18n://docs'
5052
})
51-
console.log('read resource', contents)
53+
// console.log('read resource', contents)
54+
// const page = await client.readResource({
55+
// uri: 'vue-i18n://docs{page}',
56+
// params: {
57+
// page: '/guide/installation.md'
58+
// }
59+
// })
60+
// console.log('read page', page)
5261
}
5362

5463
main()

docs/.vitepress/config.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { HeadConfig } from 'vitepress'
88

99
const head: HeadConfig[] = [['link', { rel: 'icon', href: '/vue-i18n-logo.png' }]]
1010

11-
export default defineConfig({
11+
const config = defineConfig({
1212
title: 'Vue I18n',
1313
description: 'Internationalization plugin for Vue.js',
1414

@@ -316,3 +316,5 @@ function sidebarEcosystem() {
316316
}
317317
]
318318
}
319+
320+
export default config

docs/.vitepress/mcp.ts

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
1+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
2+
import { existsSync } from 'node:fs'
13
import fs from 'node:fs/promises'
24
import path from 'node:path'
35

46
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
57
import type { ViteDevServer } from 'vite'
6-
import type { SiteConfig } from 'vitepress'
8+
import type { DefaultTheme, SiteConfig, SiteData } from 'vitepress'
79

810
type Awaitable<T> = T | PromiseLike<T>;
911

10-
export default function mcp(mcpServer: McpServer, viteServer: ViteDevServer): Awaitable<void | McpServer> {
12+
export default async function mcp(mcpServer: McpServer, viteServer: ViteDevServer): Awaitable<void | McpServer> {
1113
const vitepress = (viteServer.config as any).vitepress as SiteConfig
12-
console.log('Setting up MCP server for Vue I18n docs...', vitepress.userConfig.themeConfig)
13-
// mcpServer.resource('contents', 'vue-i18n://contents', async (uri) => {
14-
mcpServer.resource('contents', 'vue-i18n://contents', {
15-
uri: 'vue-i18n://contents',
16-
name: 'Vue I18n Contents',
17-
description: 'List of Contents for Vue I18n',
14+
const docRootDir = vitepress.srcDir
15+
const docFiles = (vitepress.pages || []).map(page => path.resolve(docRootDir, page)).filter(file => existsSync(file))
16+
console.log('VitePress pages:', docFiles)
17+
const themeConfig = vitepress.site.themeConfig as DefaultTheme.Config
18+
console.log('VitePress site config:', vitepress)
19+
// console.log('Setting up MCP server for Vue I18n docs...', themeConfig.sidebar)
20+
const sidebarDirs = await getSidebarDirNames(docRootDir)
21+
console.log('Sidebar:', themeConfig.sidebar, sidebarDirs)
22+
// for (const [key, value] of Object.entries(themeConfig.sidebar || {})) {
23+
// console.log(`Sidebar item: ${key}`)
24+
// const items = (themeConfig.sidebar || {})[key] as DefaultTheme.SidebarItem[]
25+
// for (const item of items) {
26+
// console.log(item.text, item)
27+
// }
28+
// }
29+
30+
// @ts-expect-error -- FIXME:
31+
mcpServer.resource('contents', 'vue-i18n://docs', {
32+
uri: 'vue-i18n://docs',
33+
name: 'Vue I18n Documentation top',
34+
description: 'Vue I18n documentation root, provides categories and links to documentation pages.',
1835
}, async (uri) => {
19-
const filePath = path.resolve(import.meta.dirname, './dist/llms.txt')
20-
const content = await fs.readFile(filePath, 'utf-8')
36+
const content = renderMarkdownTop(vitepress.site, getSideBar(sidebarDirs, themeConfig.sidebar || {}))
2137
return {
2238
contents: [
2339
{
@@ -27,5 +43,82 @@ export default function mcp(mcpServer: McpServer, viteServer: ViteDevServer): Aw
2743
]
2844
}
2945
})
46+
47+
const pageUri = 'vue-i18n://docs{page}'
48+
const pageTempalte = new ResourceTemplate(pageUri, { list: undefined })
49+
mcpServer.resource('pages', pageTempalte, {
50+
uri: pageUri,
51+
name: 'Vue I18n Documentation page',
52+
description: 'Vue I18n documentation page content',
53+
}, async (uri, params) => {
54+
console.log('Fetching page:', uri, params)
55+
const p = Array.isArray(params.page) ? params.page[0] : params.page
56+
const pagePath = path.resolve(docRootDir, p)
57+
const file = await fs.readFile(pagePath, 'utf-8')
58+
return {
59+
contents: [{
60+
uri: uri.href,
61+
text: file,
62+
}]
63+
}
64+
})
65+
3066
return mcpServer
3167
}
68+
69+
function renderMarkdownTop(site: SiteData, sidebar: ReturnType<typeof getSideBar>) {
70+
return `# ${site.title}
71+
72+
${site.description}
73+
74+
## Table of Contents
75+
76+
${Object.entries(sidebar).map(([_caegory, items]) => {
77+
const buf = [] as string[]
78+
items.reduce((acc, item) => {
79+
acc.push(`### ${item.text}`, '')
80+
const items = (item.items || []) as DefaultTheme.SidebarItem[]
81+
const itemBuf = [] as string[]
82+
for (const subItem of items) {
83+
if (subItem.link) {
84+
itemBuf.push(`- [${subItem.text}](${subItem.link.endsWith('.md') ? subItem.link : `${subItem.link}.md`})`)
85+
}
86+
}
87+
itemBuf.push('')
88+
89+
if (itemBuf.length) {
90+
acc.push(itemBuf.join('\n'))
91+
}
92+
return acc
93+
}, buf)
94+
return buf.join('\n')
95+
}).join('\n\n')
96+
}
97+
`
98+
}
99+
100+
function getSideBar(sidebarDirs: string[], sideBar: DefaultTheme.Sidebar) {
101+
return Object.keys(sideBar).reduce((acc, key) => {
102+
const category = key.split('/').filter(Boolean)[0]
103+
if (sidebarDirs.includes(category)) {
104+
acc[category] = sideBar[key] || []
105+
}
106+
return acc
107+
}, {} as { [key: string]: DefaultTheme.SidebarItem[] })
108+
}
109+
110+
const EXCLUDE_DIR_NAMES = [
111+
'.vitepress',
112+
'index.md',
113+
'public',
114+
]
115+
116+
async function getSidebarDirNames(root: string): Promise<string[]> {
117+
const dirs = await fs.readdir(root, { withFileTypes: true });
118+
return dirs.reduce((acc, dir) => {
119+
if (dir.isDirectory() && !EXCLUDE_DIR_NAMES.includes(dir.name)) {
120+
acc.push(dir.name)
121+
}
122+
return acc
123+
}, [] as string[])
124+
}

0 commit comments

Comments
 (0)