@sudu-cli/fronted-preview-mcp 1.0.0-beta.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 +67 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +49 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +153 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/detectors/frameworkDetector.d.ts +3 -0
- package/dist/detectors/frameworkDetector.js +160 -0
- package/dist/detectors/frameworkDetector.js.map +1 -0
- package/dist/handlers/getProjectInfo.d.ts +6 -0
- package/dist/handlers/getProjectInfo.js +30 -0
- package/dist/handlers/getProjectInfo.js.map +1 -0
- package/dist/handlers/quickPreview.d.ts +7 -0
- package/dist/handlers/quickPreview.js +57 -0
- package/dist/handlers/quickPreview.js.map +1 -0
- package/dist/handlers/startDevServer.d.ts +8 -0
- package/dist/handlers/startDevServer.js +57 -0
- package/dist/handlers/startDevServer.js.map +1 -0
- package/dist/handlers/stopDevServer.d.ts +3 -0
- package/dist/handlers/stopDevServer.js +29 -0
- package/dist/handlers/stopDevServer.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/serverManager.d.ts +8 -0
- package/dist/serverManager.js +166 -0
- package/dist/serverManager.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
- package/templates/init/agent.md +31 -0
- package/templates/init/instructions.md +33 -0
- package/templates/init/opencode.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @sudu-cli/fronted-preview-mcp
|
|
2
|
+
|
|
3
|
+
MCP server 用于前端项目框架检测和 dev server 管理。与 `chrome-devtools-mcp` 配合使用,实现自动化前端预览和错误检查工作流。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 在项目中初始化配置
|
|
9
|
+
npx @sudu-cli/fronted-preview-mcp init
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 使用方法
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 启动 MCP server(opencode 自动调用)
|
|
16
|
+
npx @sudu-cli/fronted-preview-mcp
|
|
17
|
+
|
|
18
|
+
# 在项目中初始化 opencode 配置
|
|
19
|
+
npx @sudu-cli/fronted-preview-mcp init
|
|
20
|
+
|
|
21
|
+
# 查看帮助
|
|
22
|
+
npx @sudu-cli/fronted-preview-mcp --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 工具
|
|
26
|
+
|
|
27
|
+
| 工具 | 说明 |
|
|
28
|
+
|------|------|
|
|
29
|
+
| `quick_preview` | **推荐**。检测框架 + 启动 dev server,一步完成 |
|
|
30
|
+
| `get_project_info` | 仅检测项目框架信息 |
|
|
31
|
+
| `start_dev_server` | 仅启动 dev server |
|
|
32
|
+
| `stop_dev_server` | 停止 dev server |
|
|
33
|
+
|
|
34
|
+
### 推荐工作流
|
|
35
|
+
|
|
36
|
+
调用 `quick_preview` 后,按顺序执行:
|
|
37
|
+
|
|
38
|
+
1. **chrome-devtools** `navigate_page(url)` — 打开页面
|
|
39
|
+
2. **chrome-devtools** `list_console_messages()` — 检查 console 错误
|
|
40
|
+
3. **chrome-devtools** `take_snapshot()` — 分析页面结构
|
|
41
|
+
|
|
42
|
+
## 配置
|
|
43
|
+
|
|
44
|
+
`npx @sudu-cli/fronted-preview-mcp init` 会生成:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
项目目录/
|
|
48
|
+
├── opencode.json # MCP 服务器配置
|
|
49
|
+
└── .opencode/
|
|
50
|
+
├── agents/preview-checker.md # 可选:子智能体
|
|
51
|
+
└── frontend-preview/instructions.md # 工作流说明
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 要求
|
|
55
|
+
|
|
56
|
+
- Node.js >= 18
|
|
57
|
+
- opencode 或其他 MCP 客户端
|
|
58
|
+
- chrome-devtools-mcp(自动配置)
|
|
59
|
+
|
|
60
|
+
## 开发
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install
|
|
64
|
+
npm run build # 编译 TypeScript
|
|
65
|
+
npm run dev # 监听模式
|
|
66
|
+
node dist/cli.js --help
|
|
67
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { main as runMcpServer } from './index.js';
|
|
3
|
+
async function showHelp() {
|
|
4
|
+
console.log(`
|
|
5
|
+
@sudu-cli/fronted-preview-mcp v1.0.0 — Frontend preview MCP server
|
|
6
|
+
|
|
7
|
+
USAGE:
|
|
8
|
+
fronted-preview-mcp Start MCP server (stdio — used by opencode)
|
|
9
|
+
fronted-preview-mcp init Initialize project configuration for team use
|
|
10
|
+
fronted-preview-mcp --help Show this help
|
|
11
|
+
|
|
12
|
+
DESCRIPTION:
|
|
13
|
+
An MCP server that detects frontend framework and manages dev server lifecycle.
|
|
14
|
+
Designed to work alongside chrome-devtools-mcp for automated frontend preview
|
|
15
|
+
and error checking workflows.
|
|
16
|
+
|
|
17
|
+
COMMANDS:
|
|
18
|
+
init Generate .opencode/agents/, .opencode/skills/, and opencode.json
|
|
19
|
+
for the current project. Run this once per project.
|
|
20
|
+
|
|
21
|
+
(no arguments) Default mode. Starts the MCP server with stdio transport,
|
|
22
|
+
used by opencode as a local MCP server.
|
|
23
|
+
|
|
24
|
+
EXAMPLES:
|
|
25
|
+
npx @sudu-cli/fronted-preview-mcp Start MCP server
|
|
26
|
+
npx @sudu-cli/fronted-preview-mcp init Set up project configuration
|
|
27
|
+
npx @sudu-cli/fronted-preview-mcp --help Show this help
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
async function cli() {
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
const command = args[0];
|
|
33
|
+
if (command === 'init') {
|
|
34
|
+
const { init } = await import('./commands/init.js');
|
|
35
|
+
await init(args.slice(1));
|
|
36
|
+
}
|
|
37
|
+
else if (command === '--help' || command === '-h') {
|
|
38
|
+
await showHelp();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Default: MCP server mode
|
|
42
|
+
await runMcpServer();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
cli().catch((err) => {
|
|
46
|
+
console.error('Fatal error:', err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AAElD,KAAK,UAAU,QAAQ;IACrB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACpD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;SAAM,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,QAAQ,EAAE,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,2BAA2B;QAC3B,MAAM,YAAY,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAClB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
// Get the package root directory (2 levels up from __dirname in src/commands/)
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const PKG_ROOT = path.resolve(__dirname, '..', '..');
|
|
8
|
+
const TEMPLATES_DIR = path.join(PKG_ROOT, 'templates', 'init');
|
|
9
|
+
export async function init(args) {
|
|
10
|
+
const options = parseArgs(args);
|
|
11
|
+
const targetDir = path.resolve(process.cwd(), options.targetDir);
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(' ⚡ frontend-preview init');
|
|
14
|
+
console.log(` Target: ${targetDir}`);
|
|
15
|
+
console.log('');
|
|
16
|
+
// Check if target directory exists
|
|
17
|
+
if (!fs.existsSync(targetDir)) {
|
|
18
|
+
console.error(` ✖ Directory not found: ${targetDir}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// Check if it looks like a frontend project
|
|
22
|
+
const hasPackageJson = fs.existsSync(path.join(targetDir, 'package.json'));
|
|
23
|
+
if (!hasPackageJson) {
|
|
24
|
+
console.warn(' ⚠ No package.json found — this may not be a frontend project.');
|
|
25
|
+
console.warn(' Proceeding anyway...');
|
|
26
|
+
console.warn('');
|
|
27
|
+
}
|
|
28
|
+
// 1. Create .opencode directory structure
|
|
29
|
+
const opencodeDir = path.join(targetDir, '.opencode');
|
|
30
|
+
const instructionsDir = path.join(opencodeDir, 'frontend-preview');
|
|
31
|
+
fs.mkdirSync(instructionsDir, { recursive: true });
|
|
32
|
+
console.log(` ✓ Created .opencode/frontend-preview/`);
|
|
33
|
+
// 2. Write instructions.md from template
|
|
34
|
+
const instructionsSrc = path.join(TEMPLATES_DIR, 'instructions.md');
|
|
35
|
+
const instructionsDest = path.join(instructionsDir, 'instructions.md');
|
|
36
|
+
if (fs.existsSync(instructionsDest) && !options.force) {
|
|
37
|
+
console.log(` - Skipped instructions.md (already exists, use --force to overwrite)`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (fs.existsSync(instructionsSrc)) {
|
|
41
|
+
const content = fs.readFileSync(instructionsSrc, 'utf-8');
|
|
42
|
+
fs.writeFileSync(instructionsDest, content, 'utf-8');
|
|
43
|
+
console.log(` ✓ Created .opencode/frontend-preview/instructions.md`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.warn(` ⚠ Template not found: ${instructionsSrc}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// 3. Write agent.md from template (optional — for subagent workflow)
|
|
50
|
+
const agentDir = path.join(opencodeDir, 'agents');
|
|
51
|
+
const agentSrc = path.join(TEMPLATES_DIR, 'agent.md');
|
|
52
|
+
const agentDest = path.join(agentDir, 'preview-checker.md');
|
|
53
|
+
if (fs.existsSync(agentDest) && !options.force) {
|
|
54
|
+
console.log(` - Skipped agents/preview-checker.md (already exists, use --force to overwrite)`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
if (fs.existsSync(agentSrc)) {
|
|
58
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
59
|
+
const content = fs.readFileSync(agentSrc, 'utf-8');
|
|
60
|
+
fs.writeFileSync(agentDest, content, 'utf-8');
|
|
61
|
+
console.log(` ✓ Created .opencode/agents/preview-checker.md`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.warn(` ⚠ Template not found: ${agentSrc}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 4. Update or create opencode.json
|
|
68
|
+
const configPath = path.join(targetDir, 'opencode.json');
|
|
69
|
+
let existingConfig = {};
|
|
70
|
+
if (fs.existsSync(configPath)) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
73
|
+
existingConfig = JSON.parse(raw);
|
|
74
|
+
console.log(` ✓ Found existing opencode.json — merging config`);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
console.warn(` ⚠ Could not parse existing opencode.json, will create new one`);
|
|
78
|
+
existingConfig = {};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Merge MCP servers config
|
|
82
|
+
if (!existingConfig['mcp']) {
|
|
83
|
+
existingConfig['mcp'] = {};
|
|
84
|
+
}
|
|
85
|
+
const mcp = existingConfig['mcp'];
|
|
86
|
+
if (!mcp['frontend-preview']) {
|
|
87
|
+
mcp['frontend-preview'] = {
|
|
88
|
+
type: 'local',
|
|
89
|
+
command: ['npx', '-y', 'frontend-preview'],
|
|
90
|
+
};
|
|
91
|
+
console.log(` ✓ Added frontend-preview MCP server`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log(` - frontend-preview MCP already configured`);
|
|
95
|
+
}
|
|
96
|
+
if (!mcp['chrome-devtools']) {
|
|
97
|
+
mcp['chrome-devtools'] = {
|
|
98
|
+
type: 'local',
|
|
99
|
+
command: ['npx', '-y', 'chrome-devtools-mcp@latest'],
|
|
100
|
+
};
|
|
101
|
+
console.log(` ✓ Added chrome-devtools MCP server`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(` - chrome-devtools MCP already configured`);
|
|
105
|
+
}
|
|
106
|
+
// Merge instructions
|
|
107
|
+
const instructionsPath = '.opencode/frontend-preview/instructions.md';
|
|
108
|
+
if (!existingConfig['instructions']) {
|
|
109
|
+
existingConfig['instructions'] = [instructionsPath];
|
|
110
|
+
console.log(` ✓ Added instructions reference`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const instructions = existingConfig['instructions'];
|
|
114
|
+
if (!instructions.includes(instructionsPath)) {
|
|
115
|
+
instructions.push(instructionsPath);
|
|
116
|
+
console.log(` ✓ Added instructions.md to instructions list`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(` - instructions.md already referenced`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Ensure $schema
|
|
123
|
+
if (!existingConfig['$schema']) {
|
|
124
|
+
existingConfig['$schema'] = 'https://opencode.ai/config.json';
|
|
125
|
+
}
|
|
126
|
+
// Write updated config
|
|
127
|
+
fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2) + '\n', 'utf-8');
|
|
128
|
+
console.log(` ✓ Updated opencode.json`);
|
|
129
|
+
// Summary
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(' ─────────────────────────────────────────────');
|
|
132
|
+
console.log(' ✅ Setup complete!');
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(' Next steps:');
|
|
135
|
+
console.log(` 1. cd ${path.relative(process.cwd(), targetDir) || '.'}`);
|
|
136
|
+
console.log(' 2. opencode');
|
|
137
|
+
console.log(' 3. Say: "检查一下页面效果"');
|
|
138
|
+
console.log('');
|
|
139
|
+
}
|
|
140
|
+
function parseArgs(args) {
|
|
141
|
+
let targetDir = '.';
|
|
142
|
+
let force = false;
|
|
143
|
+
for (let i = 0; i < args.length; i++) {
|
|
144
|
+
if (args[i] === '--force' || args[i] === '-f') {
|
|
145
|
+
force = true;
|
|
146
|
+
}
|
|
147
|
+
else if (!args[i].startsWith('-')) {
|
|
148
|
+
targetDir = args[i];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { targetDir, force };
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const FRAMEWORK_CONFIGS = [
|
|
4
|
+
{
|
|
5
|
+
framework: 'vite',
|
|
6
|
+
port: 5173,
|
|
7
|
+
configFiles: ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'],
|
|
8
|
+
depKeys: ['vite'],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
framework: 'nextjs',
|
|
12
|
+
port: 3000,
|
|
13
|
+
configFiles: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
14
|
+
depKeys: ['next'],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
framework: 'vue-cli',
|
|
18
|
+
port: 8080,
|
|
19
|
+
configFiles: ['vue.config.js'],
|
|
20
|
+
depKeys: ['@vue/cli-service'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
framework: 'cra',
|
|
24
|
+
port: 3000,
|
|
25
|
+
configFiles: [],
|
|
26
|
+
depKeys: ['react-scripts'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
framework: 'webpack',
|
|
30
|
+
port: 8080,
|
|
31
|
+
configFiles: ['webpack.config.js', 'webpack.config.ts'],
|
|
32
|
+
depKeys: ['webpack-dev-server', 'webpack'],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
const DEV_SCRIPT_NAMES = ['dev', 'serve', 'start'];
|
|
36
|
+
async function fileExists(filePath) {
|
|
37
|
+
try {
|
|
38
|
+
await fs.access(filePath);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function extractPortFromConfig(configContent) {
|
|
46
|
+
const portMatch = configContent.match(/port\s*[=:]\s*(\d{4,5})/);
|
|
47
|
+
if (portMatch) {
|
|
48
|
+
return parseInt(portMatch[1], 10);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
async function detectFramework(projectDir) {
|
|
53
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
54
|
+
let pkg = { scripts: {}, dependencies: {}, devDependencies: {} };
|
|
55
|
+
try {
|
|
56
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
57
|
+
pkg = JSON.parse(pkgContent);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return {
|
|
61
|
+
framework: 'unknown',
|
|
62
|
+
port: 0,
|
|
63
|
+
devCommand: '',
|
|
64
|
+
devScript: '',
|
|
65
|
+
projectName: path.basename(projectDir),
|
|
66
|
+
hasNodeModules: await fileExists(path.join(projectDir, 'node_modules')),
|
|
67
|
+
npmScripts: {},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const allDeps = {
|
|
71
|
+
...pkg.dependencies,
|
|
72
|
+
...(pkg.devDependencies || {}),
|
|
73
|
+
};
|
|
74
|
+
const scripts = pkg.scripts || {};
|
|
75
|
+
const projectName = pkg.name || path.basename(projectDir);
|
|
76
|
+
const hasNodeModules = await fileExists(path.join(projectDir, 'node_modules'));
|
|
77
|
+
// Check each framework in priority order
|
|
78
|
+
for (const cfg of FRAMEWORK_CONFIGS) {
|
|
79
|
+
// Check config files
|
|
80
|
+
for (const configFile of cfg.configFiles) {
|
|
81
|
+
const configPath = path.join(projectDir, configFile);
|
|
82
|
+
if (await fileExists(configPath)) {
|
|
83
|
+
// Read config to extract custom port
|
|
84
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
85
|
+
const customPort = extractPortFromConfig(content);
|
|
86
|
+
return {
|
|
87
|
+
framework: cfg.framework,
|
|
88
|
+
port: customPort ?? cfg.port,
|
|
89
|
+
devCommand: findDevCommand(scripts, cfg.framework),
|
|
90
|
+
devScript: findDevScriptName(scripts),
|
|
91
|
+
projectName,
|
|
92
|
+
hasNodeModules,
|
|
93
|
+
npmScripts: scripts,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check dependencies
|
|
98
|
+
for (const key of cfg.depKeys) {
|
|
99
|
+
if (allDeps[key]) {
|
|
100
|
+
return {
|
|
101
|
+
framework: cfg.framework,
|
|
102
|
+
port: cfg.port,
|
|
103
|
+
devCommand: findDevCommand(scripts, cfg.framework),
|
|
104
|
+
devScript: findDevScriptName(scripts),
|
|
105
|
+
projectName,
|
|
106
|
+
hasNodeModules,
|
|
107
|
+
npmScripts: scripts,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Fallback: try to find any dev script
|
|
113
|
+
const devScript = findDevScriptName(scripts);
|
|
114
|
+
if (devScript) {
|
|
115
|
+
return {
|
|
116
|
+
framework: 'unknown',
|
|
117
|
+
port: 5173, // best guess
|
|
118
|
+
devCommand: scripts[devScript],
|
|
119
|
+
devScript,
|
|
120
|
+
projectName,
|
|
121
|
+
hasNodeModules,
|
|
122
|
+
npmScripts: scripts,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
framework: 'unknown',
|
|
127
|
+
port: 0,
|
|
128
|
+
devCommand: '',
|
|
129
|
+
devScript: '',
|
|
130
|
+
projectName,
|
|
131
|
+
hasNodeModules,
|
|
132
|
+
npmScripts: scripts,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function findDevScriptName(scripts) {
|
|
136
|
+
for (const name of DEV_SCRIPT_NAMES) {
|
|
137
|
+
if (scripts[name])
|
|
138
|
+
return name;
|
|
139
|
+
}
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
function findDevCommand(scripts, framework) {
|
|
143
|
+
if (scripts.dev)
|
|
144
|
+
return scripts.dev;
|
|
145
|
+
if (scripts.serve)
|
|
146
|
+
return scripts.serve;
|
|
147
|
+
if (scripts.start)
|
|
148
|
+
return scripts.start;
|
|
149
|
+
// Framework-specific defaults
|
|
150
|
+
const defaults = {
|
|
151
|
+
vite: 'vite',
|
|
152
|
+
nextjs: 'next dev',
|
|
153
|
+
'vue-cli': 'vue-cli-service serve',
|
|
154
|
+
cra: 'react-scripts start',
|
|
155
|
+
webpack: 'webpack serve --mode development',
|
|
156
|
+
};
|
|
157
|
+
return defaults[framework] || 'npm run dev';
|
|
158
|
+
}
|
|
159
|
+
export { detectFramework };
|
|
160
|
+
//# sourceMappingURL=frameworkDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameworkDetector.js","sourceRoot":"","sources":["../../src/detectors/frameworkDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { detectFramework } from '../detectors/frameworkDetector.js';
|
|
2
|
+
async function handleGetProjectInfo(args) {
|
|
3
|
+
try {
|
|
4
|
+
const projectDir = args.projectDir || process.cwd();
|
|
5
|
+
const info = await detectFramework(projectDir);
|
|
6
|
+
const result = {
|
|
7
|
+
projectName: info.projectName,
|
|
8
|
+
framework: info.framework,
|
|
9
|
+
port: info.port,
|
|
10
|
+
devScript: info.devScript,
|
|
11
|
+
devCommand: info.devCommand,
|
|
12
|
+
hasNodeModules: info.hasNodeModules,
|
|
13
|
+
hasDevScript: !!info.devScript,
|
|
14
|
+
npmScripts: info.npmScripts,
|
|
15
|
+
projectDir,
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export { handleGetProjectInfo };
|
|
30
|
+
//# sourceMappingURL=getProjectInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getProjectInfo.js","sourceRoot":"","sources":["../../src/handlers/getProjectInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAOpE,KAAK,UAAU,oBAAoB,CAAC,IAAwB;IAC1D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS;YAC9B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,UAAU;SACX,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { detectFramework } from '../detectors/frameworkDetector.js';
|
|
2
|
+
import { startServer } from '../serverManager.js';
|
|
3
|
+
async function handleQuickPreview(args) {
|
|
4
|
+
try {
|
|
5
|
+
const projectDir = args.projectDir || process.cwd();
|
|
6
|
+
// Step 1: Detect framework
|
|
7
|
+
const info = await detectFramework(projectDir);
|
|
8
|
+
if (!info.devScript) {
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: JSON.stringify({
|
|
14
|
+
error: 'No dev script found',
|
|
15
|
+
availableScripts: Object.keys(info.npmScripts),
|
|
16
|
+
projectDir,
|
|
17
|
+
}, null, 2),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Step 2: Start dev server (if not already running)
|
|
24
|
+
const port = args.customPort || info.port;
|
|
25
|
+
const command = `npm run ${info.devScript}`;
|
|
26
|
+
const status = await startServer(projectDir, command, port);
|
|
27
|
+
// Return combined result
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: JSON.stringify({
|
|
33
|
+
url: status.url,
|
|
34
|
+
port: status.port,
|
|
35
|
+
ready: status.ready,
|
|
36
|
+
framework: info.framework,
|
|
37
|
+
projectName: info.projectName,
|
|
38
|
+
devScript: info.devScript,
|
|
39
|
+
devCommand: info.devCommand,
|
|
40
|
+
pid: status.pid,
|
|
41
|
+
startedAt: status.startedAt,
|
|
42
|
+
projectDir,
|
|
43
|
+
}, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) }],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export { handleQuickPreview };
|
|
57
|
+
//# sourceMappingURL=quickPreview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quickPreview.js","sourceRoot":"","sources":["../../src/handlers/quickPreview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAmB,MAAM,qBAAqB,CAAC;AAQnE,KAAK,UAAU,kBAAkB,CAAC,IAAsB;IACtD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAEpD,2BAA2B;QAC3B,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;QAE/C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;4BACE,KAAK,EAAE,qBAAqB;4BAC5B,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;4BAC9C,UAAU;yBACX,EACD,IAAI,EACJ,CAAC,CACF;qBACF;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,oDAAoD;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1C,MAAM,OAAO,GAAG,WAAW,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAE5D,yBAAyB;QACzB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;wBACE,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;wBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,WAAW,EAAE,IAAI,CAAC,WAAW;wBAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,UAAU,EAAE,IAAI,CAAC,UAAU;wBAC3B,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,UAAU;qBACX,EACD,IAAI,EACJ,CAAC,CACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MCPToolResponse } from '../types.js';
|
|
2
|
+
interface StartDevServerArgs {
|
|
3
|
+
projectDir?: string;
|
|
4
|
+
customPort?: number;
|
|
5
|
+
customCommand?: string;
|
|
6
|
+
}
|
|
7
|
+
declare function handleStartDevServer(args: StartDevServerArgs): Promise<MCPToolResponse>;
|
|
8
|
+
export { handleStartDevServer };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { detectFramework } from '../detectors/frameworkDetector.js';
|
|
2
|
+
import { startServer } from '../serverManager.js';
|
|
3
|
+
async function handleStartDevServer(args) {
|
|
4
|
+
try {
|
|
5
|
+
const projectDir = args.projectDir || process.cwd();
|
|
6
|
+
// Detect framework to get port and command
|
|
7
|
+
const info = await detectFramework(projectDir);
|
|
8
|
+
if (!info.devScript && !args.customCommand) {
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: JSON.stringify({
|
|
14
|
+
error: 'No dev script found. Available scripts: ' + JSON.stringify(info.npmScripts),
|
|
15
|
+
projectDir,
|
|
16
|
+
}, null, 2),
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const port = args.customPort || info.port;
|
|
23
|
+
const command = args.customCommand || `${info.devScript}`;
|
|
24
|
+
const npmCommand = `npm run ${command}`;
|
|
25
|
+
// If it's a script name, resolve to npm run <script>
|
|
26
|
+
const resolvedCommand = info.npmScripts[command]
|
|
27
|
+
? `npm run ${command}`
|
|
28
|
+
: command;
|
|
29
|
+
const status = await startServer(projectDir, resolvedCommand, port);
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: JSON.stringify({
|
|
35
|
+
url: status.url,
|
|
36
|
+
port: status.port,
|
|
37
|
+
pid: status.pid,
|
|
38
|
+
ready: status.ready,
|
|
39
|
+
startedAt: status.startedAt,
|
|
40
|
+
framework: info.framework,
|
|
41
|
+
projectName: info.projectName,
|
|
42
|
+
command: resolvedCommand,
|
|
43
|
+
}, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) }],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export { handleStartDevServer };
|
|
57
|
+
//# sourceMappingURL=startDevServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startDevServer.js","sourceRoot":"","sources":["../../src/handlers/startDevServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASlD,KAAK,UAAU,oBAAoB,CAAC,IAAwB;IAC1D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAEpD,2CAA2C;QAC3C,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;QAE/C,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3C,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;4BACE,KAAK,EAAE,0CAA0C,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC;4BACnF,UAAU;yBACX,EACD,IAAI,EACJ,CAAC,CACF;qBACF;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1D,MAAM,UAAU,GAAG,WAAW,OAAO,EAAE,CAAC;QAExC,qDAAqD;QACrD,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC9C,CAAC,CAAC,WAAW,OAAO,EAAE;YACtB,CAAC,CAAC,OAAO,CAAC;QAEZ,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,eAAe,EAAE,IAAI,CAAC,CAAC;QAEpE,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;wBACE,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,KAAK,EAAE,MAAM,CAAC,KAAK;wBACnB,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,WAAW,EAAE,IAAI,CAAC,WAAW;wBAC7B,OAAO,EAAE,eAAe;qBACzB,EACD,IAAI,EACJ,CAAC,CACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { stopServer, getServerStatus } from '../serverManager.js';
|
|
2
|
+
async function handleStopDevServer() {
|
|
3
|
+
try {
|
|
4
|
+
const wasRunning = getServerStatus();
|
|
5
|
+
const result = await stopServer();
|
|
6
|
+
return {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: JSON.stringify({
|
|
11
|
+
success: result.success,
|
|
12
|
+
message: result.message,
|
|
13
|
+
wasRunning: !!wasRunning,
|
|
14
|
+
previousUrl: wasRunning?.url || null,
|
|
15
|
+
}, null, 2),
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) }],
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export { handleStopDevServer };
|
|
29
|
+
//# sourceMappingURL=stopDevServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stopDevServer.js","sourceRoot":"","sources":["../../src/handlers/stopDevServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGlE,KAAK,UAAU,mBAAmB;IAChC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAElC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;wBACE,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,UAAU,EAAE,CAAC,CAAC,UAAU;wBACxB,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI,IAAI;qBACrC,EACD,IAAI,EACJ,CAAC,CACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { handleGetProjectInfo } from './handlers/getProjectInfo.js';
|
|
7
|
+
import { handleStartDevServer } from './handlers/startDevServer.js';
|
|
8
|
+
import { handleStopDevServer } from './handlers/stopDevServer.js';
|
|
9
|
+
import { handleQuickPreview } from './handlers/quickPreview.js';
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: 'frontend-preview',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
}, {
|
|
14
|
+
capabilities: {
|
|
15
|
+
tools: {},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
// ============================================================
|
|
19
|
+
// Tool: get_project_info
|
|
20
|
+
// ============================================================
|
|
21
|
+
server.tool('get_project_info', 'Detect frontend project framework and dev server settings. ' +
|
|
22
|
+
'Scans for vite.config.*, next.config.*, webpack.config.*, or reads package.json ' +
|
|
23
|
+
'dependencies to identify framework (Vite, Next.js, Vue CLI, CRA, Webpack). ' +
|
|
24
|
+
'Returns { framework, port, script, scripts, projectDir }.\n\n' +
|
|
25
|
+
'WHEN TO USE:\n' +
|
|
26
|
+
'- User asks "what framework is this?" or "what project is this?"\n' +
|
|
27
|
+
'- You need framework info before deciding how to start the server\n\n' +
|
|
28
|
+
'WHEN NOT TO USE:\n' +
|
|
29
|
+
'- User wants to preview or check the page — use quick_preview instead\n' +
|
|
30
|
+
'- User explicitly asks to start the dev server — use quick_preview\n\n' +
|
|
31
|
+
'AFTER THIS TOOL:\n' +
|
|
32
|
+
'- If you need to start the server, call start_dev_server (or quick_preview)\n' +
|
|
33
|
+
'- The returned "script" field tells you which npm script to run', {
|
|
34
|
+
projectDir: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Absolute path to the frontend project directory. Defaults to current working directory.'),
|
|
38
|
+
}, async (args) => {
|
|
39
|
+
return await handleGetProjectInfo(args);
|
|
40
|
+
});
|
|
41
|
+
// ============================================================
|
|
42
|
+
// Tool: start_dev_server
|
|
43
|
+
// ============================================================
|
|
44
|
+
server.tool('start_dev_server', 'Start the frontend dev server. Detects framework automatically, ' +
|
|
45
|
+
'resolves the dev script (npm run dev/serve/start), launches it as a ' +
|
|
46
|
+
'child process, and waits for the port to be ready.\n\n' +
|
|
47
|
+
'Returns { url, framework, port, script, ready }.\n\n' +
|
|
48
|
+
'WHEN TO USE:\n' +
|
|
49
|
+
'- Only when you ALREADY know the framework from get_project_info\n' +
|
|
50
|
+
'- Or when you need to customize port/command\n\n' +
|
|
51
|
+
'WHEN NOT TO USE:\n' +
|
|
52
|
+
'- If you haven\'t called get_project_info yet — use quick_preview instead\n' +
|
|
53
|
+
'- For most preview scenarios, quick_preview is simpler\n\n' +
|
|
54
|
+
'AFTER THIS TOOL:\n' +
|
|
55
|
+
'- Call chrome-devtools navigate_page with the returned URL\n' +
|
|
56
|
+
'- Then call list_console_messages to check for errors\n' +
|
|
57
|
+
'- Then call take_snapshot to inspect page structure', {
|
|
58
|
+
projectDir: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Absolute path to the frontend project directory.'),
|
|
62
|
+
customPort: z
|
|
63
|
+
.number()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('Override the auto-detected port number.'),
|
|
66
|
+
customCommand: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Override the dev command (e.g. "start" or "vite --port 3000").'),
|
|
70
|
+
}, async (args) => {
|
|
71
|
+
return await handleStartDevServer(args);
|
|
72
|
+
});
|
|
73
|
+
// ============================================================
|
|
74
|
+
// Tool: stop_dev_server
|
|
75
|
+
// ============================================================
|
|
76
|
+
server.tool('stop_dev_server', 'Stop the dev server started by start_dev_server or quick_preview. ' +
|
|
77
|
+
'Kills the child process and cleans up resources. ' +
|
|
78
|
+
'Safe to call even if no server is running.\n\n' +
|
|
79
|
+
'WHEN TO USE:\n' +
|
|
80
|
+
'- User explicitly says "stop the server" or "shut it down"\n\n' +
|
|
81
|
+
'WHEN NOT TO USE:\n' +
|
|
82
|
+
'- Do NOT call this automatically after preview — the server can stay running\n' +
|
|
83
|
+
'- Let the user explicitly ask to stop it', {}, async () => {
|
|
84
|
+
return await handleStopDevServer();
|
|
85
|
+
});
|
|
86
|
+
// ============================================================
|
|
87
|
+
// Tool: quick_preview
|
|
88
|
+
// ============================================================
|
|
89
|
+
server.tool('quick_preview', 'ONE-STEP: detect frontend framework + start dev server. ' +
|
|
90
|
+
'Combines get_project_info and start_dev_server into a single call. ' +
|
|
91
|
+
'This is the PREFERRED tool for most scenarios.\n\n' +
|
|
92
|
+
'Returns { url, framework, port, script, ready, projectDir }.\n\n' +
|
|
93
|
+
'WHEN TO USE (PREFERRED):\n' +
|
|
94
|
+
'- User says "check the page", "preview", "see how it looks"\n' +
|
|
95
|
+
'- User says "start the dev server" or "run the project"\n' +
|
|
96
|
+
'- User reports something looks wrong on the page\n' +
|
|
97
|
+
'- ANY scenario where you need the page running\n\n' +
|
|
98
|
+
'WHEN NOT TO USE:\n' +
|
|
99
|
+
'- User only asks about project info — use get_project_info\n' +
|
|
100
|
+
'- User explicitly asks to stop — use stop_dev_server\n\n' +
|
|
101
|
+
'CRITICAL — AFTER THIS TOOL, USE THIS SEQUENCE:\n' +
|
|
102
|
+
'1. chrome-devtools navigate_page(url) ← Open page in browser\n' +
|
|
103
|
+
'2. chrome-devtools list_console_messages() ← Check for JS errors\n' +
|
|
104
|
+
'3. chrome-devtools take_snapshot() ← Inspect page structure\n' +
|
|
105
|
+
'4. Report findings to the user\n\n' +
|
|
106
|
+
'Do NOT call get_project_info or start_dev_server separately when using this.\n' +
|
|
107
|
+
'The dev server stays running — no need to call stop_dev_server after preview.', {
|
|
108
|
+
projectDir: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe('Absolute path to the frontend project directory. Defaults to current working directory.'),
|
|
112
|
+
customPort: z
|
|
113
|
+
.number()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('Override the auto-detected port number. Only use if the user specifies a different port.'),
|
|
116
|
+
}, async (args) => {
|
|
117
|
+
return await handleQuickPreview(args);
|
|
118
|
+
});
|
|
119
|
+
// ============================================================
|
|
120
|
+
// Server startup
|
|
121
|
+
// ============================================================
|
|
122
|
+
export async function main() {
|
|
123
|
+
const transport = new StdioServerTransport();
|
|
124
|
+
await server.connect(transport);
|
|
125
|
+
}
|
|
126
|
+
// When executed directly: `node dist/index.js` or `npx frontend-preview`
|
|
127
|
+
const isMainModule = process.argv[1] &&
|
|
128
|
+
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
129
|
+
if (isMainModule) {
|
|
130
|
+
main().catch((err) => {
|
|
131
|
+
console.error('Fatal error:', err);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ServerStatus } from './types.js';
|
|
2
|
+
declare function startServer(projectDir: string, devCommand: string, port: number): Promise<ServerStatus>;
|
|
3
|
+
declare function stopServer(): Promise<{
|
|
4
|
+
success: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
}>;
|
|
7
|
+
declare function getServerStatus(): ServerStatus | null;
|
|
8
|
+
export { startServer, stopServer, getServerStatus };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import * as net from 'node:net';
|
|
3
|
+
let activeServer = null;
|
|
4
|
+
function isPortAvailable(port) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const server = net.createServer();
|
|
7
|
+
server.once('error', () => resolve(false));
|
|
8
|
+
server.once('listening', () => {
|
|
9
|
+
server.close();
|
|
10
|
+
resolve(true);
|
|
11
|
+
});
|
|
12
|
+
server.listen(port, '127.0.0.1');
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function waitForPort(port, timeoutMs = 30000) {
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const check = async () => {
|
|
19
|
+
const available = await isPortAvailable(port);
|
|
20
|
+
if (!available) {
|
|
21
|
+
resolve(true); // port is in use = server is ready
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (Date.now() - start > timeoutMs) {
|
|
25
|
+
resolve(false); // timeout
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setTimeout(check, 500);
|
|
29
|
+
};
|
|
30
|
+
check();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function startServer(projectDir, devCommand, port) {
|
|
34
|
+
// If already running, return current status
|
|
35
|
+
if (activeServer) {
|
|
36
|
+
const isRunning = activeServer.process.exitCode === null;
|
|
37
|
+
if (isRunning) {
|
|
38
|
+
return {
|
|
39
|
+
url: activeServer.url,
|
|
40
|
+
port: activeServer.port,
|
|
41
|
+
pid: activeServer.process.pid || 0,
|
|
42
|
+
ready: true,
|
|
43
|
+
startedAt: new Date(activeServer.startTime).toISOString(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Process died, clean up
|
|
47
|
+
activeServer = null;
|
|
48
|
+
}
|
|
49
|
+
// Check if port is already in use (server running externally)
|
|
50
|
+
const portAvailable = await isPortAvailable(port);
|
|
51
|
+
if (!portAvailable) {
|
|
52
|
+
return {
|
|
53
|
+
url: `http://localhost:${port}`,
|
|
54
|
+
port,
|
|
55
|
+
pid: 0,
|
|
56
|
+
ready: true,
|
|
57
|
+
startedAt: new Date().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Determine shell command
|
|
61
|
+
const isWin = process.platform === 'win32';
|
|
62
|
+
const shellCmd = isWin ? `cmd /c "${devCommand}"` : devCommand;
|
|
63
|
+
const child = spawn(shellCmd, [], {
|
|
64
|
+
cwd: projectDir,
|
|
65
|
+
shell: true,
|
|
66
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
});
|
|
69
|
+
child.stdout?.on('data', (data) => {
|
|
70
|
+
// Optionally log or filter output
|
|
71
|
+
const text = data.toString();
|
|
72
|
+
// Vite prints local URL on stdout
|
|
73
|
+
if (text.includes('Local:')) {
|
|
74
|
+
const urlMatch = text.match(/http:\/\/localhost:\d+/);
|
|
75
|
+
if (urlMatch) {
|
|
76
|
+
// URL is confirmed
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
child.stderr?.on('data', (data) => {
|
|
81
|
+
// Some frameworks output to stderr
|
|
82
|
+
});
|
|
83
|
+
child.on('error', (err) => {
|
|
84
|
+
activeServer = null;
|
|
85
|
+
throw new Error(`Failed to start dev server: ${err.message}`);
|
|
86
|
+
});
|
|
87
|
+
child.on('exit', (code) => {
|
|
88
|
+
if (activeServer?.process === child) {
|
|
89
|
+
activeServer = null;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// Wait for port to be ready
|
|
93
|
+
const ready = await waitForPort(port, 30000);
|
|
94
|
+
if (!ready) {
|
|
95
|
+
// Kill the process if it didn't start in time
|
|
96
|
+
child.kill();
|
|
97
|
+
throw new Error(`Dev server did not start within 30 seconds on port ${port}`);
|
|
98
|
+
}
|
|
99
|
+
const url = `http://localhost:${port}`;
|
|
100
|
+
activeServer = {
|
|
101
|
+
process: child,
|
|
102
|
+
port,
|
|
103
|
+
url,
|
|
104
|
+
startTime: Date.now(),
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
url,
|
|
108
|
+
port,
|
|
109
|
+
pid: child.pid || 0,
|
|
110
|
+
ready: true,
|
|
111
|
+
startedAt: new Date().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function stopServer() {
|
|
115
|
+
if (!activeServer) {
|
|
116
|
+
return { success: true, message: 'No active dev server to stop.' };
|
|
117
|
+
}
|
|
118
|
+
const child = activeServer.process;
|
|
119
|
+
const port = activeServer.port;
|
|
120
|
+
try {
|
|
121
|
+
// Try graceful shutdown first
|
|
122
|
+
const isWin = process.platform === 'win32';
|
|
123
|
+
if (isWin) {
|
|
124
|
+
spawn('taskkill', ['/pid', String(child.pid), '/f', '/t']);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
child.kill('SIGTERM');
|
|
128
|
+
}
|
|
129
|
+
// Wait for process to exit
|
|
130
|
+
await new Promise((resolve) => {
|
|
131
|
+
const timeout = setTimeout(() => {
|
|
132
|
+
child.kill('SIGKILL');
|
|
133
|
+
resolve();
|
|
134
|
+
}, 5000);
|
|
135
|
+
child.on('exit', () => {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
activeServer = null;
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
message: `Dev server on port ${port} stopped.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
message: `Failed to stop dev server: ${errorMessage}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getServerStatus() {
|
|
155
|
+
if (!activeServer)
|
|
156
|
+
return null;
|
|
157
|
+
return {
|
|
158
|
+
url: activeServer.url,
|
|
159
|
+
port: activeServer.port,
|
|
160
|
+
pid: activeServer.process.pid || 0,
|
|
161
|
+
ready: activeServer.process.exitCode === null,
|
|
162
|
+
startedAt: new Date(activeServer.startTime).toISOString(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export { startServer, stopServer, getServerStatus };
|
|
166
|
+
//# sourceMappingURL=serverManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverManager.js","sourceRoot":"","sources":["../src/serverManager.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ProjectInfo {
|
|
2
|
+
framework: 'vite' | 'nextjs' | 'vue-cli' | 'cra' | 'webpack' | 'unknown';
|
|
3
|
+
port: number;
|
|
4
|
+
devCommand: string;
|
|
5
|
+
devScript: string;
|
|
6
|
+
projectName: string;
|
|
7
|
+
hasNodeModules: boolean;
|
|
8
|
+
npmScripts: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface ServerStatus {
|
|
11
|
+
url: string;
|
|
12
|
+
port: number;
|
|
13
|
+
pid: number;
|
|
14
|
+
ready: boolean;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MCPToolResponse {
|
|
18
|
+
content: Array<{
|
|
19
|
+
type: 'text';
|
|
20
|
+
text: string;
|
|
21
|
+
}>;
|
|
22
|
+
isError?: boolean;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sudu-cli/fronted-preview-mcp",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"description": "MCP server for frontend project detection, dev server management, and preview workflow. Pairs with chrome-devtools-mcp for automated frontend checking.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"fronted-preview-mcp": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"@types/node": "^22.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"frontend",
|
|
37
|
+
"preview",
|
|
38
|
+
"opencode",
|
|
39
|
+
"claude-code",
|
|
40
|
+
"vite",
|
|
41
|
+
"nextjs",
|
|
42
|
+
"dev-server"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 前端页面预览检查专家,自动检测 console 错误和渲染问题
|
|
3
|
+
mode: subagent
|
|
4
|
+
permission:
|
|
5
|
+
edit: deny
|
|
6
|
+
bash: deny
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
你是一个前端页面检查专家,专注于自动预览前端页面并报告问题。
|
|
10
|
+
|
|
11
|
+
## 工作流程
|
|
12
|
+
|
|
13
|
+
1. 调用 `quick_preview` 检测框架并启动 dev server
|
|
14
|
+
2. 用 chrome-devtools `navigate_page` 打开返回的 URL
|
|
15
|
+
3. 用 `list_console_messages` 检查 console 错误
|
|
16
|
+
4. 用 `take_snapshot` 获取页面结构分析
|
|
17
|
+
5. 汇总结果给用户
|
|
18
|
+
|
|
19
|
+
## 工具路由
|
|
20
|
+
|
|
21
|
+
| 场景 | 工具序列 |
|
|
22
|
+
|------|---------|
|
|
23
|
+
| "检查页面效果" | quick_preview → navigate_page → list_console_messages → take_snapshot |
|
|
24
|
+
| "只看项目信息" | get_project_info(不启动服务) |
|
|
25
|
+
| "停止 dev server" | stop_dev_server |
|
|
26
|
+
|
|
27
|
+
## 注意
|
|
28
|
+
|
|
29
|
+
- 不需要截图,take_snapshot 就够了
|
|
30
|
+
- quick_preview 已包含检测,不要再调 get_project_info
|
|
31
|
+
- 报告结果时说明:是否正常运行、是否有错误、页面结构
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Frontend Preview Workflow
|
|
2
|
+
|
|
3
|
+
本项目已配置 `frontend-preview` + `chrome-devtools` MCP 服务器。
|
|
4
|
+
你可以用自然语言描述前端页面查看需求,AI 会自动使用下面的工作流。
|
|
5
|
+
|
|
6
|
+
## 页面预览检查工作流
|
|
7
|
+
|
|
8
|
+
当用户想"检查页面效果"、"预览页面"、"看看页面有什么问题"时,按照以下步骤:
|
|
9
|
+
|
|
10
|
+
1. **`quick_preview`** — 检测框架 + 启动 dev server,一步完成
|
|
11
|
+
2. **chrome-devtools `navigate_page`** — 在浏览器中打开页面 URL
|
|
12
|
+
3. **chrome-devtools `list_console_messages`** — 检查 console 错误
|
|
13
|
+
4. **chrome-devtools `take_snapshot`** — 获取页面无障碍结构树,分析渲染
|
|
14
|
+
5. **汇总结果** — 告诉用户:页面运行状态、有无 console 错误、页面结构概况
|
|
15
|
+
|
|
16
|
+
## 工具快速参考
|
|
17
|
+
|
|
18
|
+
| 工具 | 用途 | 优先使用场景 |
|
|
19
|
+
|------|------|------------|
|
|
20
|
+
| `quick_preview` | 检测框架 + 启动 dev server | 大多数场景——一步到位 |
|
|
21
|
+
| `get_project_info` | 仅检测框架信息 | 用户只问"这是什么项目" |
|
|
22
|
+
| `start_dev_server` | 仅启动 dev server | 已知道框架时单独使用 |
|
|
23
|
+
| `stop_dev_server` | 停止 dev server | 用户明确要求停止 |
|
|
24
|
+
| chrome-devtools `navigate_page` | 打开 URL | 拿到 URL 后立即使用 |
|
|
25
|
+
| chrome-devtools `list_console_messages` | 检查 console 错误 | 页面加载完成后 |
|
|
26
|
+
| chrome-devtools `take_snapshot` | 获取页面结构 | 需要分析渲染结果时 |
|
|
27
|
+
| chrome-devtools `take_screenshot` | 页面截图 | 用户明确要求视觉确认时 |
|
|
28
|
+
|
|
29
|
+
## 注意
|
|
30
|
+
|
|
31
|
+
- 默认不需要截图,`take_snapshot` 的无障碍树对 AI 已足够
|
|
32
|
+
- `quick_preview` 已包含 framework 检测,不要再重复调 `get_project_info`
|
|
33
|
+
- dev server 可以保持运行,不需要每次用完就关闭
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"mcp": {
|
|
4
|
+
"frontend-preview": {
|
|
5
|
+
"type": "local",
|
|
6
|
+
"command": ["npx", "-y", "@sudu-cli/fronted-preview-mcp"]
|
|
7
|
+
},
|
|
8
|
+
"chrome-devtools": {
|
|
9
|
+
"type": "local",
|
|
10
|
+
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"instructions": [".opencode/frontend-preview/instructions.md"]
|
|
14
|
+
}
|