@quasar/mcp 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/package.json +61 -0
- package/src/api.js +272 -0
- package/src/bin.js +63 -0
- package/src/docs.js +606 -0
- package/src/index.js +2 -0
- package/src/project.js +189 -0
- package/src/server.js +526 -0
- package/src/slugify.js +21 -0
- package/src/updates.js +41 -0
- package/src/version.js +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-present Razvan Stoenescu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# @quasar/mcp
|
|
4
|
+
|
|
5
|
+
> An [MCP](https://modelcontextprotocol.io) server that gives AI agents the Quasar documentation and component API of the exact versions installed in your project.
|
|
6
|
+
|
|
7
|
+
<img alt="@quasar/mcp npm version" src="https://img.shields.io/npm/v/%40quasar/mcp.svg?label=@quasar/mcp">
|
|
8
|
+
|
|
9
|
+
[](https://github.com/quasarframework/quasar/actions/workflows/mcp-tests.yml)
|
|
10
|
+
|
|
11
|
+
The documentation pages ship inside the `quasar` and `@quasar/app-vite` packages (in their `dist/mcp` folder), and the component API inside `quasar` (`dist/api`), so the server works offline and always describes what you run. There is no fallback to quasar.dev: a project on releases predating the bundled docs gets the API only, and the server says so.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Add the server to your MCP client, from the project folder. Nothing gets installed in the project: `npx` starts the latest release when online and the cached copy when offline (the retries flag keeps that fallback quick):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"quasar": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "--fetch-retries=0", "@quasar/mcp@latest"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The server serves the project it is started in. Clients that start servers elsewhere can point it at the project with `--project <dir>`.
|
|
29
|
+
|
|
30
|
+
See the [AI Agents](https://quasar.dev/start/ai-agents) page for the per-client instructions.
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
|
|
34
|
+
| Tool | What it returns |
|
|
35
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `list_pages` | every page available offline with its approximate size, grouped by the package shipping it |
|
|
37
|
+
| `search_docs` | the pages matching some keywords, with their description, approximate size and the sections the keywords occur in |
|
|
38
|
+
| `get_page` | one page, one section of it (by heading, or the `#anchor` of a pasted link), or its outline, as markdown |
|
|
39
|
+
| `list_api` | the names of the API descriptors: components, plugins, directives, utilities |
|
|
40
|
+
| `get_api` | the props, slots, events and methods of one of them, one part, or one member, as the site shows them; `format: "json"` for the raw descriptor |
|
|
41
|
+
| `check_updates` | whether newer releases of quasar, @quasar/app-vite or this server exist |
|
|
42
|
+
|
|
43
|
+
At session start the server also tells the agent which package versions it serves, what is missing, and which updates are available (checked in the background, cached for a day, never while offline, the same mechanism the Quasar CLI uses; `NO_UPDATE_NOTIFIER` disables it).
|
|
44
|
+
|
|
45
|
+
## Chat Support
|
|
46
|
+
|
|
47
|
+
Ask questions at the official community Discord server: [https://chat.quasar.dev](https://chat.quasar.dev)
|
|
48
|
+
|
|
49
|
+
## Community Forum
|
|
50
|
+
|
|
51
|
+
Ask questions at the official community forum: [https://forum.quasar.dev](https://forum.quasar.dev)
|
|
52
|
+
|
|
53
|
+
## Contributing
|
|
54
|
+
|
|
55
|
+
I'm excited if you want to contribute to Quasar under any form (report bugs, write a plugin, fix an issue, write a new feature). Please read the [Contributing Guide](../CONTRIBUTING.md).
|
|
56
|
+
|
|
57
|
+
## Semver
|
|
58
|
+
|
|
59
|
+
Quasar is following [Semantic Versioning 2.0](https://semver.org/).
|
|
60
|
+
|
|
61
|
+
The documentation slices the packages ship (`dist/mcp`) carry a `format` number in their `meta.json`. It moves only when the shape the server parses changes, and this server's major version tracks it: `@quasar/mcp@1` reads format 1. A project whose installed packages carry another format is told so at session start, with the server to run instead.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
Copyright (c) 2026-present Razvan Stoenescu
|
|
66
|
+
|
|
67
|
+
[MIT License](http://en.wikipedia.org/wiki/MIT_License)
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quasar/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for AI agents: the Quasar documentation and component API, served from the packages installed in your project",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"ai",
|
|
8
|
+
"docs",
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"quasar",
|
|
12
|
+
"vue"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://quasar.dev",
|
|
15
|
+
"bugs": "https://github.com/quasarframework/quasar/issues",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Razvan Stoenescu",
|
|
19
|
+
"email": "razvan.stoenescu@gmail.com",
|
|
20
|
+
"url": "https://github.com/quasarframework"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/quasarframework/quasar",
|
|
25
|
+
"directory": "mcp"
|
|
26
|
+
},
|
|
27
|
+
"funding": {
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://donate.quasar.dev"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"quasar-mcp": "./src/bin.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"!src/**/*.test.js",
|
|
37
|
+
"!src/test"
|
|
38
|
+
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"exports": "./src/index.js",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
46
|
+
"@quasar/update-notifier": "^1.1.0",
|
|
47
|
+
"zod": "^4.6.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"vitest": "^5.0.1"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">= 22.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "pnpm test:unit && pnpm test:e2e",
|
|
57
|
+
"test:unit": "vitest run",
|
|
58
|
+
"pretest:e2e": "pnpm --filter quasar generate:mcp && pnpm --filter @quasar/app-vite generate:mcp",
|
|
59
|
+
"test:e2e": "vitest run -c vitest-e2e.config.js"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { extractSection } from './docs.js'
|
|
5
|
+
|
|
6
|
+
/** The sections of an API descriptor `get_api` can serve on their own. */
|
|
7
|
+
export const API_PARTS = [
|
|
8
|
+
'props',
|
|
9
|
+
'slots',
|
|
10
|
+
'events',
|
|
11
|
+
'methods',
|
|
12
|
+
'computedProps',
|
|
13
|
+
'value',
|
|
14
|
+
'arg',
|
|
15
|
+
'modifiers',
|
|
16
|
+
'injection',
|
|
17
|
+
'quasarConfOptions'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The heading(s) the docs renderer (docs/build/mcp/api/render.js)
|
|
22
|
+
* gives each part in `api/<Name>.md`.
|
|
23
|
+
*/
|
|
24
|
+
const PART_HEADINGS = {
|
|
25
|
+
props: ['Props'],
|
|
26
|
+
slots: ['Slots', 'Scoped Slots'],
|
|
27
|
+
events: ['Events'],
|
|
28
|
+
methods: ['Methods'],
|
|
29
|
+
computedProps: ['Computed Props'],
|
|
30
|
+
value: ['Directive Value'],
|
|
31
|
+
arg: ['Directive Argument'],
|
|
32
|
+
modifiers: ['Directive Modifiers'],
|
|
33
|
+
injection: ['Vue Injection'],
|
|
34
|
+
quasarConfOptions: ['quasar.config.js Options']
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The parts whose entries are named members `get_api` can serve one of. */
|
|
38
|
+
export const MEMBER_PARTS = [
|
|
39
|
+
'props',
|
|
40
|
+
'computedProps',
|
|
41
|
+
'methods',
|
|
42
|
+
'events',
|
|
43
|
+
'slots',
|
|
44
|
+
'modifiers'
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Member names compare without case or punctuation: `modelValue`,
|
|
49
|
+
* `model-value` and the rendered `@update:model-value` all meet.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} value
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function normalizeMember(value) {
|
|
55
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9]/g, '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cache = new Map()
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} apiDir
|
|
62
|
+
* @returns {string[]} Descriptor names (`QBtn`, `Notify`, `Ripple`, ...), sorted.
|
|
63
|
+
*/
|
|
64
|
+
export function listApi(apiDir) {
|
|
65
|
+
return readdirSync(apiDir)
|
|
66
|
+
.filter(file => file.endsWith('.json'))
|
|
67
|
+
.map(file => file.slice(0, -'.json'.length))
|
|
68
|
+
.sort()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} apiDir
|
|
73
|
+
* @param {string} name
|
|
74
|
+
* @returns {Record<string, unknown> | null}
|
|
75
|
+
*/
|
|
76
|
+
export function readApi(apiDir, name) {
|
|
77
|
+
const file = join(apiDir, `${name}.json`)
|
|
78
|
+
let api = cache.get(file)
|
|
79
|
+
if (api === void 0) {
|
|
80
|
+
try {
|
|
81
|
+
api = JSON.parse(readFileSync(file, 'utf8'))
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
cache.set(file, api)
|
|
86
|
+
}
|
|
87
|
+
return api
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Case-insensitive lookup that also forgives a missing `Q` prefix, so
|
|
92
|
+
* `btn`, `qbtn` and `QBtn` all find QBtn.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} apiDir
|
|
95
|
+
* @param {string} name
|
|
96
|
+
* @returns {string | null} The canonical descriptor name.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveApiName(apiDir, name) {
|
|
99
|
+
const names = listApi(apiDir)
|
|
100
|
+
const wanted = name.trim().toLowerCase()
|
|
101
|
+
return (
|
|
102
|
+
names.find(known => known.toLowerCase() === wanted) ??
|
|
103
|
+
names.find(known => known.toLowerCase() === `q${wanted}`) ??
|
|
104
|
+
null
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} apiDir
|
|
110
|
+
* @param {string} name
|
|
111
|
+
* @returns {string[]} Names containing the input, for a miss.
|
|
112
|
+
*/
|
|
113
|
+
export function similarApiNames(apiDir, name) {
|
|
114
|
+
const needle = name.trim().toLowerCase().replace(/^q/, '')
|
|
115
|
+
return listApi(apiDir)
|
|
116
|
+
.filter(known => known.toLowerCase().includes(needle))
|
|
117
|
+
.slice(0, 8)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The descriptor as the site inlines it (`api/<Name>.md` in the ui
|
|
122
|
+
* slice, rendered by the docs generator), whole or one part. Null when
|
|
123
|
+
* the installed release bundles no rendered form, or the part has no
|
|
124
|
+
* section there.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} docsDir The ui slice (`dist/mcp`).
|
|
127
|
+
* @param {string} name
|
|
128
|
+
* @param {string} [part]
|
|
129
|
+
* @returns {string | null}
|
|
130
|
+
*/
|
|
131
|
+
export function readApiMarkdown(docsDir, name, part) {
|
|
132
|
+
const markdown = readApiFile(docsDir, name)
|
|
133
|
+
if (markdown === null) {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
if (part === void 0) {
|
|
137
|
+
return markdown
|
|
138
|
+
}
|
|
139
|
+
const sections = PART_HEADINGS[part]
|
|
140
|
+
.map(heading => extractSection(markdown, heading))
|
|
141
|
+
.filter(section => section !== null)
|
|
142
|
+
return sections.length === 0 ? null : sections.join('\n\n') + '\n'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {string} docsDir
|
|
147
|
+
* @param {string} name
|
|
148
|
+
* @returns {string | null}
|
|
149
|
+
*/
|
|
150
|
+
function readApiFile(docsDir, name) {
|
|
151
|
+
const file = join(docsDir, 'api', `${name}.md`)
|
|
152
|
+
let markdown = cache.get(file)
|
|
153
|
+
if (markdown === void 0) {
|
|
154
|
+
if (!existsSync(file)) {
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
markdown = readFileSync(file, 'utf8')
|
|
158
|
+
cache.set(file, markdown)
|
|
159
|
+
}
|
|
160
|
+
return markdown
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The members of a descriptor named like `member`, across the parts
|
|
165
|
+
* that have named members or in the one given.
|
|
166
|
+
*
|
|
167
|
+
* @param {Record<string, unknown>} api
|
|
168
|
+
* @param {string} member
|
|
169
|
+
* @param {string} [part]
|
|
170
|
+
* @returns {Array<{ part: string, name: string }>}
|
|
171
|
+
*/
|
|
172
|
+
export function findApiMembers(api, member, part) {
|
|
173
|
+
const wanted = normalizeMember(member)
|
|
174
|
+
const matches = []
|
|
175
|
+
for (const candidate of part === void 0 ? MEMBER_PARTS : [part]) {
|
|
176
|
+
const entries = api[candidate]
|
|
177
|
+
if (!entries || typeof entries !== 'object') {
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
for (const name of Object.keys(entries)) {
|
|
181
|
+
if (normalizeMember(name) === wanted) {
|
|
182
|
+
matches.push({ part: candidate, name })
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return matches
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {Record<string, unknown>} api
|
|
191
|
+
* @param {string} member
|
|
192
|
+
* @param {string} [part]
|
|
193
|
+
* @returns {string[]} `part.name` of the members containing the input, for a miss.
|
|
194
|
+
*/
|
|
195
|
+
export function similarApiMembers(api, member, part) {
|
|
196
|
+
const needle = normalizeMember(member)
|
|
197
|
+
const similar = []
|
|
198
|
+
for (const candidate of part === void 0 ? MEMBER_PARTS : [part]) {
|
|
199
|
+
const entries = api[candidate]
|
|
200
|
+
if (!entries || typeof entries !== 'object') {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
for (const name of Object.keys(entries)) {
|
|
204
|
+
if (needle !== '' && normalizeMember(name).includes(needle)) {
|
|
205
|
+
similar.push(`${candidate}.${name}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return similar.slice(0, 8)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The name a rendered entry line (`- \`pagination\``, `- \`@click\``,
|
|
214
|
+
* `- \`#default\``, `- \`toggle(): void\``) is about, null for any
|
|
215
|
+
* other line.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} line
|
|
218
|
+
* @returns {string | null}
|
|
219
|
+
*/
|
|
220
|
+
function entryName(line) {
|
|
221
|
+
const match = /^- `([^`]+)`/.exec(line)
|
|
222
|
+
return match === null
|
|
223
|
+
? null
|
|
224
|
+
: match[1].replace(/^[@#]/, '').replace(/\(.*$/, '')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The rendered entries of the given members, each under its section
|
|
229
|
+
* heading. Null when the release bundles no rendered form or none of
|
|
230
|
+
* the members has an entry there.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} docsDir
|
|
233
|
+
* @param {string} name
|
|
234
|
+
* @param {Array<{ part: string, name: string }>} members From findApiMembers().
|
|
235
|
+
* @returns {string | null}
|
|
236
|
+
*/
|
|
237
|
+
export function readApiMembersMarkdown(docsDir, name, members) {
|
|
238
|
+
const markdown = readApiFile(docsDir, name)
|
|
239
|
+
if (markdown === null) {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
const found = []
|
|
243
|
+
for (const member of members) {
|
|
244
|
+
const wanted = normalizeMember(member.name)
|
|
245
|
+
for (const heading of PART_HEADINGS[member.part]) {
|
|
246
|
+
const section = extractSection(markdown, heading)
|
|
247
|
+
if (section === null) {
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
const [headingLine, ...lines] = section.split('\n')
|
|
251
|
+
let entry = null
|
|
252
|
+
for (const line of lines) {
|
|
253
|
+
const current = entryName(line)
|
|
254
|
+
if (current !== null) {
|
|
255
|
+
if (entry !== null) {
|
|
256
|
+
break
|
|
257
|
+
}
|
|
258
|
+
if (normalizeMember(current) === wanted) {
|
|
259
|
+
entry = [line]
|
|
260
|
+
}
|
|
261
|
+
} else if (entry !== null) {
|
|
262
|
+
entry.push(line)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (entry !== null) {
|
|
266
|
+
found.push(`${headingLine}\n\n${entry.join('\n').trim()}`)
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return found.length === 0 ? null : found.join('\n\n') + '\n'
|
|
272
|
+
}
|
package/src/bin.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from 'node:util'
|
|
4
|
+
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
6
|
+
|
|
7
|
+
import { loadProject } from './project.js'
|
|
8
|
+
import { createServer } from './server.js'
|
|
9
|
+
import { version } from './version.js'
|
|
10
|
+
|
|
11
|
+
const usage = `Usage: quasar-mcp [--project <dir>]
|
|
12
|
+
|
|
13
|
+
MCP server (stdio) for the Quasar documentation and component API of the
|
|
14
|
+
packages installed in a project. Point your MCP client at it, e.g.:
|
|
15
|
+
|
|
16
|
+
{ "mcpServers": { "quasar": { "command": "npx", "args": ["-y", "--fetch-retries=0", "@quasar/mcp@latest"] } } }
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--project <dir> Project to serve, the one whose installed quasar and
|
|
20
|
+
@quasar/app-vite carry the docs (default: cwd; when
|
|
21
|
+
cwd has none, the first app found below it is served)
|
|
22
|
+
-v, --version Print the version and exit
|
|
23
|
+
-h, --help Print this help and exit
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
let values
|
|
27
|
+
try {
|
|
28
|
+
;({ values } = parseArgs({
|
|
29
|
+
options: {
|
|
30
|
+
project: { type: 'string' },
|
|
31
|
+
version: { type: 'boolean', short: 'v' },
|
|
32
|
+
help: { type: 'boolean', short: 'h' }
|
|
33
|
+
},
|
|
34
|
+
strict: true
|
|
35
|
+
}))
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(err.message)
|
|
38
|
+
console.error(usage)
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (values.help === true) {
|
|
43
|
+
console.log(usage)
|
|
44
|
+
process.exit()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (values.version === true) {
|
|
48
|
+
console.log(version)
|
|
49
|
+
process.exit()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// stdout carries the protocol: anything else a dependency might print
|
|
53
|
+
// there would corrupt the session, so route every console channel to
|
|
54
|
+
// stderr, which MCP clients keep as the server's log.
|
|
55
|
+
for (const channel of ['log', 'info', 'debug', 'table', 'dir']) {
|
|
56
|
+
console[channel] = console.error
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const project = loadProject(values.project, {
|
|
60
|
+
explicit: values.project !== void 0
|
|
61
|
+
})
|
|
62
|
+
const server = await createServer({ project })
|
|
63
|
+
await server.connect(new StdioServerTransport())
|