@weapp-vite/mcp 1.0.0 → 1.1.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 ADDED
@@ -0,0 +1,60 @@
1
+ # @weapp-vite/mcp
2
+
3
+ ## 简介
4
+
5
+ `@weapp-vite/mcp` 是面向 `weapp-vite` / `wevu` monorepo 的 MCP 服务端实现,目标是把核心研发能力暴露给 AI:
6
+
7
+ - 包目录与能力发现
8
+ - 源码读取、检索、按行裁剪
9
+ - 包级脚本执行
10
+ - `weapp-vite` CLI 执行
11
+ - 文档/变更记录资源暴露
12
+ - 调试/改造提示词模板
13
+
14
+ 默认通过 `stdio` 运行,适合接入任意 MCP Client。
15
+
16
+ ## 启动
17
+
18
+ ```bash
19
+ pnpm --filter @weapp-vite/mcp start
20
+ ```
21
+
22
+ ## 主要 Tools
23
+
24
+ - `workspace_catalog`: 输出 `weapp-vite / wevu / wevu-compiler` 目录、版本、脚本
25
+ - `list_source_files`: 列出包内文件(默认 `src`)
26
+ - `read_source_file`: 读取包内文件,支持 `startLine/endLine/maxChars`
27
+ - `search_source_code`: 在包源码中检索关键词
28
+ - `run_package_script`: 在指定包目录执行 `pnpm run <script>`
29
+ - `run_weapp_vite_cli`: 执行 `node packages/weapp-vite/bin/weapp-vite.js ...`
30
+ - `run_repo_command`: 执行仓库级命令(`pnpm/node/git/rg`)
31
+
32
+ ## 主要 Resources
33
+
34
+ - `weapp-vite://workspace/catalog`
35
+ - `weapp-vite://docs/{package}/README.md`
36
+ - `weapp-vite://docs/{package}/CHANGELOG.md`
37
+ - `weapp-vite://source/{package}?path={path}`
38
+
39
+ 其中 `{package}` 支持:
40
+
41
+ - `weapp-vite`
42
+ - `wevu`
43
+ - `wevu-compiler`
44
+
45
+ ## Prompts
46
+
47
+ - `plan-weapp-vite-change`: 生成 weapp-vite/wevu 改造计划提示词
48
+ - `debug-wevu-runtime`: 生成 wevu runtime 排查提示词
49
+
50
+ ## 开发
51
+
52
+ ```bash
53
+ pnpm --filter @weapp-vite/mcp test
54
+ pnpm --filter @weapp-vite/mcp build
55
+ ```
56
+
57
+ ## 相关链接
58
+
59
+ - MCP SDK: https://github.com/modelcontextprotocol/sdk
60
+ - 仓库: https://github.com/weapp-vite/weapp-vite
package/dist/index.d.mts CHANGED
@@ -1,2 +1,114 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
2
 
2
- export { };
3
+ declare const MCP_SERVER_NAME = "@weapp-vite/mcp";
4
+ declare const MCP_SERVER_VERSION = "2.0.0";
5
+ declare const EXPOSED_PACKAGES: {
6
+ readonly 'weapp-vite': {
7
+ readonly id: "weapp-vite";
8
+ readonly label: "weapp-vite";
9
+ readonly relativePath: string;
10
+ };
11
+ readonly wevu: {
12
+ readonly id: "wevu";
13
+ readonly label: "wevu";
14
+ readonly relativePath: string;
15
+ };
16
+ readonly 'wevu-compiler': {
17
+ readonly id: "wevu-compiler";
18
+ readonly label: "wevu-compiler";
19
+ readonly relativePath: string;
20
+ };
21
+ };
22
+ type ExposedPackageId = keyof typeof EXPOSED_PACKAGES;
23
+ declare const DEFAULT_TIMEOUT_MS = 120000;
24
+ declare const DEFAULT_MAX_OUTPUT_CHARS = 120000;
25
+ declare const DEFAULT_MAX_FILE_CHARS = 80000;
26
+ declare const DEFAULT_MAX_RESULTS = 200;
27
+ declare const SKIPPED_DIR_NAMES: Set<string>;
28
+
29
+ interface ExposedPackageSummary {
30
+ id: ExposedPackageId;
31
+ label: string;
32
+ packageName: string;
33
+ version: string;
34
+ absolutePath: string;
35
+ relativePath: string;
36
+ scripts: string[];
37
+ docs: {
38
+ readme?: string;
39
+ changelog?: string;
40
+ };
41
+ }
42
+ declare function loadPackageSummary(workspaceRoot: string, id: ExposedPackageId): Promise<ExposedPackageSummary>;
43
+ declare function loadExposedCatalog(workspaceRoot: string): Promise<ExposedPackageSummary[]>;
44
+
45
+ interface CommandResult {
46
+ command: string;
47
+ args: string[];
48
+ cwd: string;
49
+ exitCode: number;
50
+ stdout: string;
51
+ stderr: string;
52
+ timedOut: boolean;
53
+ }
54
+ declare function runCommand(workspaceRoot: string, command: string, args: string[], options?: {
55
+ cwdRelative?: string;
56
+ timeoutMs?: number;
57
+ maxOutputChars?: number;
58
+ }): Promise<CommandResult>;
59
+
60
+ interface SearchMatch {
61
+ filePath: string;
62
+ line: number;
63
+ column: number;
64
+ text: string;
65
+ }
66
+ declare function listFilesInDirectory(root: string, relativeDirectory?: string, maxResults?: number): Promise<string[]>;
67
+ declare function readFileContent(root: string, relativeFilePath: string, options?: {
68
+ startLine?: number;
69
+ endLine?: number;
70
+ maxChars?: number;
71
+ }): Promise<{
72
+ filePath: string;
73
+ content: string;
74
+ }>;
75
+ declare function searchTextInDirectory(root: string, query: string, options?: {
76
+ relativeDirectory?: string;
77
+ maxResults?: number;
78
+ }): Promise<SearchMatch[]>;
79
+
80
+ interface CreateServerOptions {
81
+ workspaceRoot?: string;
82
+ }
83
+ declare function createWeappViteMcpServer(options?: CreateServerOptions): Promise<{
84
+ server: McpServer;
85
+ workspaceRoot: string;
86
+ }>;
87
+
88
+ declare function formatJson(value: unknown): string;
89
+ declare function normalizeErrorMessage(error: unknown): string;
90
+ declare function toToolResult(data: unknown, text?: string): {
91
+ content: {
92
+ type: "text";
93
+ text: string;
94
+ }[];
95
+ structuredContent: {
96
+ result: unknown;
97
+ };
98
+ };
99
+ declare function toToolError(error: unknown): {
100
+ isError: boolean;
101
+ content: {
102
+ type: "text";
103
+ text: string;
104
+ }[];
105
+ };
106
+
107
+ declare function resolveWorkspaceRoot(start?: string): string;
108
+ declare function assertInsideRoot(root: string, targetPath: string): string;
109
+ declare function resolveSubPath(root: string, relativePath: string): string;
110
+
111
+ declare function startStdioServer(): Promise<void>;
112
+
113
+ export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, assertInsideRoot, createWeappViteMcpServer, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, toToolError, toToolResult };
114
+ export type { CommandResult, CreateServerOptions, ExposedPackageId, ExposedPackageSummary, SearchMatch };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,114 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
2
 
2
- export { };
3
+ declare const MCP_SERVER_NAME = "@weapp-vite/mcp";
4
+ declare const MCP_SERVER_VERSION = "2.0.0";
5
+ declare const EXPOSED_PACKAGES: {
6
+ readonly 'weapp-vite': {
7
+ readonly id: "weapp-vite";
8
+ readonly label: "weapp-vite";
9
+ readonly relativePath: string;
10
+ };
11
+ readonly wevu: {
12
+ readonly id: "wevu";
13
+ readonly label: "wevu";
14
+ readonly relativePath: string;
15
+ };
16
+ readonly 'wevu-compiler': {
17
+ readonly id: "wevu-compiler";
18
+ readonly label: "wevu-compiler";
19
+ readonly relativePath: string;
20
+ };
21
+ };
22
+ type ExposedPackageId = keyof typeof EXPOSED_PACKAGES;
23
+ declare const DEFAULT_TIMEOUT_MS = 120000;
24
+ declare const DEFAULT_MAX_OUTPUT_CHARS = 120000;
25
+ declare const DEFAULT_MAX_FILE_CHARS = 80000;
26
+ declare const DEFAULT_MAX_RESULTS = 200;
27
+ declare const SKIPPED_DIR_NAMES: Set<string>;
28
+
29
+ interface ExposedPackageSummary {
30
+ id: ExposedPackageId;
31
+ label: string;
32
+ packageName: string;
33
+ version: string;
34
+ absolutePath: string;
35
+ relativePath: string;
36
+ scripts: string[];
37
+ docs: {
38
+ readme?: string;
39
+ changelog?: string;
40
+ };
41
+ }
42
+ declare function loadPackageSummary(workspaceRoot: string, id: ExposedPackageId): Promise<ExposedPackageSummary>;
43
+ declare function loadExposedCatalog(workspaceRoot: string): Promise<ExposedPackageSummary[]>;
44
+
45
+ interface CommandResult {
46
+ command: string;
47
+ args: string[];
48
+ cwd: string;
49
+ exitCode: number;
50
+ stdout: string;
51
+ stderr: string;
52
+ timedOut: boolean;
53
+ }
54
+ declare function runCommand(workspaceRoot: string, command: string, args: string[], options?: {
55
+ cwdRelative?: string;
56
+ timeoutMs?: number;
57
+ maxOutputChars?: number;
58
+ }): Promise<CommandResult>;
59
+
60
+ interface SearchMatch {
61
+ filePath: string;
62
+ line: number;
63
+ column: number;
64
+ text: string;
65
+ }
66
+ declare function listFilesInDirectory(root: string, relativeDirectory?: string, maxResults?: number): Promise<string[]>;
67
+ declare function readFileContent(root: string, relativeFilePath: string, options?: {
68
+ startLine?: number;
69
+ endLine?: number;
70
+ maxChars?: number;
71
+ }): Promise<{
72
+ filePath: string;
73
+ content: string;
74
+ }>;
75
+ declare function searchTextInDirectory(root: string, query: string, options?: {
76
+ relativeDirectory?: string;
77
+ maxResults?: number;
78
+ }): Promise<SearchMatch[]>;
79
+
80
+ interface CreateServerOptions {
81
+ workspaceRoot?: string;
82
+ }
83
+ declare function createWeappViteMcpServer(options?: CreateServerOptions): Promise<{
84
+ server: McpServer;
85
+ workspaceRoot: string;
86
+ }>;
87
+
88
+ declare function formatJson(value: unknown): string;
89
+ declare function normalizeErrorMessage(error: unknown): string;
90
+ declare function toToolResult(data: unknown, text?: string): {
91
+ content: {
92
+ type: "text";
93
+ text: string;
94
+ }[];
95
+ structuredContent: {
96
+ result: unknown;
97
+ };
98
+ };
99
+ declare function toToolError(error: unknown): {
100
+ isError: boolean;
101
+ content: {
102
+ type: "text";
103
+ text: string;
104
+ }[];
105
+ };
106
+
107
+ declare function resolveWorkspaceRoot(start?: string): string;
108
+ declare function assertInsideRoot(root: string, targetPath: string): string;
109
+ declare function resolveSubPath(root: string, relativePath: string): string;
110
+
111
+ declare function startStdioServer(): Promise<void>;
112
+
113
+ export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, assertInsideRoot, createWeappViteMcpServer, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, toToolError, toToolResult };
114
+ export type { CommandResult, CreateServerOptions, ExposedPackageId, ExposedPackageSummary, SearchMatch };
package/dist/index.mjs CHANGED
@@ -1,37 +1,668 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import process from 'node:process';
2
+ import { fileURLToPath } from 'node:url';
2
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import fs$1 from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
3
7
  import { z } from 'zod';
8
+ import fs from 'node:fs';
9
+ import { spawn } from 'node:child_process';
4
10
 
5
- const server = new McpServer({
6
- name: "Demo",
7
- version: "1.0.0"
8
- });
9
- server.tool(
10
- "calculate-bmi",
11
- {
12
- weightKg: z.number(),
13
- heightM: z.number()
11
+ const MCP_SERVER_NAME = "@weapp-vite/mcp";
12
+ const MCP_SERVER_VERSION = "2.0.0";
13
+ const EXPOSED_PACKAGES = {
14
+ "weapp-vite": {
15
+ id: "weapp-vite",
16
+ label: "weapp-vite",
17
+ relativePath: path.join("packages", "weapp-vite")
14
18
  },
15
- async ({ weightKg, heightM }) => ({
19
+ "wevu": {
20
+ id: "wevu",
21
+ label: "wevu",
22
+ relativePath: path.join("packages", "wevu")
23
+ },
24
+ "wevu-compiler": {
25
+ id: "wevu-compiler",
26
+ label: "wevu-compiler",
27
+ relativePath: path.join("packages", "wevu-compiler")
28
+ }
29
+ };
30
+ const DEFAULT_TIMEOUT_MS = 12e4;
31
+ const DEFAULT_MAX_OUTPUT_CHARS = 12e4;
32
+ const DEFAULT_MAX_FILE_CHARS = 8e4;
33
+ const DEFAULT_MAX_RESULTS = 200;
34
+ const SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
35
+ "node_modules",
36
+ ".git",
37
+ ".turbo",
38
+ "dist",
39
+ "coverage"
40
+ ]);
41
+
42
+ function hasWorkspaceMarkers(dir) {
43
+ return fs.existsSync(path.join(dir, "pnpm-workspace.yaml")) && fs.existsSync(path.join(dir, "package.json"));
44
+ }
45
+ function resolveWorkspaceRoot(start = process.cwd()) {
46
+ let current = path.resolve(start);
47
+ while (true) {
48
+ if (hasWorkspaceMarkers(current)) {
49
+ return current;
50
+ }
51
+ const parent = path.dirname(current);
52
+ if (parent === current) {
53
+ return path.resolve(start);
54
+ }
55
+ current = parent;
56
+ }
57
+ }
58
+ function assertInsideRoot(root, targetPath) {
59
+ const resolvedRoot = path.resolve(root);
60
+ const resolvedTarget = path.resolve(targetPath);
61
+ const relative = path.relative(resolvedRoot, resolvedTarget);
62
+ if (relative === "" || relative === ".") {
63
+ return resolvedTarget;
64
+ }
65
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
66
+ throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${targetPath}`);
67
+ }
68
+ return resolvedTarget;
69
+ }
70
+ function resolveSubPath(root, relativePath) {
71
+ if (!relativePath || relativePath === ".") {
72
+ return path.resolve(root);
73
+ }
74
+ if (path.isAbsolute(relativePath)) {
75
+ throw new Error("\u4EC5\u652F\u6301\u76F8\u5BF9\u8DEF\u5F84");
76
+ }
77
+ return assertInsideRoot(root, path.resolve(root, relativePath));
78
+ }
79
+
80
+ async function readJsonFile(filePath) {
81
+ const content = await fs$1.readFile(filePath, "utf8");
82
+ return JSON.parse(content);
83
+ }
84
+ async function pathExists(filePath) {
85
+ try {
86
+ await fs$1.access(filePath);
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+ async function loadPackageSummary(workspaceRoot, id) {
93
+ const config = EXPOSED_PACKAGES[id];
94
+ const absolutePath = assertInsideRoot(workspaceRoot, path.join(workspaceRoot, config.relativePath));
95
+ const packageJsonPath = path.join(absolutePath, "package.json");
96
+ const packageJson = await readJsonFile(packageJsonPath);
97
+ const readmePath = path.join(absolutePath, "README.md");
98
+ const changelogPath = path.join(absolutePath, "CHANGELOG.md");
99
+ return {
100
+ id,
101
+ label: config.label,
102
+ packageName: packageJson.name ?? config.label,
103
+ version: packageJson.version ?? "0.0.0",
104
+ absolutePath,
105
+ relativePath: config.relativePath,
106
+ scripts: Object.keys(packageJson.scripts ?? {}),
107
+ docs: {
108
+ readme: await pathExists(readmePath) ? readmePath : void 0,
109
+ changelog: await pathExists(changelogPath) ? changelogPath : void 0
110
+ }
111
+ };
112
+ }
113
+ async function loadExposedCatalog(workspaceRoot) {
114
+ const summaries = await Promise.all(
115
+ Object.keys(EXPOSED_PACKAGES).map((id) => loadPackageSummary(workspaceRoot, id))
116
+ );
117
+ return summaries.sort((a, b) => a.id.localeCompare(b.id));
118
+ }
119
+
120
+ const ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
121
+ "pnpm",
122
+ "node",
123
+ "git",
124
+ "rg"
125
+ ]);
126
+ function resolveExecutable(command) {
127
+ if (process.platform === "win32") {
128
+ if (command === "pnpm") {
129
+ return "pnpm.cmd";
130
+ }
131
+ if (command === "git") {
132
+ return "git.exe";
133
+ }
134
+ if (command === "rg") {
135
+ return "rg.exe";
136
+ }
137
+ }
138
+ return command;
139
+ }
140
+ function truncateOutput(text, maxChars) {
141
+ if (text.length <= maxChars) {
142
+ return text;
143
+ }
144
+ return `${text.slice(0, maxChars)}
145
+
146
+ [truncated: ${text.length - maxChars} chars omitted]`;
147
+ }
148
+ async function runCommand(workspaceRoot, command, args, options) {
149
+ if (!ALLOWED_COMMANDS.has(command)) {
150
+ throw new Error(`\u4E0D\u5141\u8BB8\u7684\u547D\u4EE4\uFF1A${command}`);
151
+ }
152
+ const cwd = resolveSubPath(workspaceRoot, options?.cwdRelative ?? ".");
153
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
154
+ const maxOutputChars = options?.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
155
+ const executable = resolveExecutable(command);
156
+ const child = spawn(executable, args, {
157
+ cwd,
158
+ env: process.env,
159
+ stdio: ["ignore", "pipe", "pipe"],
160
+ shell: false
161
+ });
162
+ let stdout = "";
163
+ let stderr = "";
164
+ let timedOut = false;
165
+ const timer = setTimeout(() => {
166
+ timedOut = true;
167
+ child.kill("SIGTERM");
168
+ }, timeoutMs);
169
+ child.stdout.on("data", (chunk) => {
170
+ stdout += chunk.toString();
171
+ });
172
+ child.stderr.on("data", (chunk) => {
173
+ stderr += chunk.toString();
174
+ });
175
+ const exitCode = await new Promise((resolve, reject) => {
176
+ child.on("error", reject);
177
+ child.on("close", (code) => {
178
+ resolve(code ?? -1);
179
+ });
180
+ }).finally(() => {
181
+ clearTimeout(timer);
182
+ });
183
+ return {
184
+ command,
185
+ args,
186
+ cwd: path.resolve(cwd),
187
+ exitCode,
188
+ stdout: truncateOutput(stdout.trim(), maxOutputChars),
189
+ stderr: truncateOutput(stderr.trim(), maxOutputChars),
190
+ timedOut
191
+ };
192
+ }
193
+
194
+ async function walkFilesRecursive(root, current, output, maxResults) {
195
+ if (output.length >= maxResults) {
196
+ return;
197
+ }
198
+ const entries = await fs$1.readdir(current, { withFileTypes: true });
199
+ for (const entry of entries) {
200
+ if (output.length >= maxResults) {
201
+ return;
202
+ }
203
+ if (entry.isDirectory()) {
204
+ if (SKIPPED_DIR_NAMES.has(entry.name)) {
205
+ continue;
206
+ }
207
+ await walkFilesRecursive(root, path.join(current, entry.name), output, maxResults);
208
+ continue;
209
+ }
210
+ if (!entry.isFile()) {
211
+ continue;
212
+ }
213
+ const absolutePath = path.join(current, entry.name);
214
+ const relativePath = path.relative(root, absolutePath).split(path.sep).join("/");
215
+ output.push(relativePath);
216
+ }
217
+ }
218
+ async function listFilesInDirectory(root, relativeDirectory = ".", maxResults = DEFAULT_MAX_RESULTS) {
219
+ const dirPath = resolveSubPath(root, relativeDirectory);
220
+ const files = [];
221
+ await walkFilesRecursive(root, dirPath, files, maxResults);
222
+ return files;
223
+ }
224
+ function sliceLines(content, startLine, endLine) {
225
+ if (!startLine && !endLine) {
226
+ return content;
227
+ }
228
+ const lines = content.split("\n");
229
+ const safeStart = Math.max(1, startLine ?? 1);
230
+ const safeEnd = Math.max(safeStart, endLine ?? lines.length);
231
+ return lines.slice(safeStart - 1, safeEnd).join("\n");
232
+ }
233
+ async function readFileContent(root, relativeFilePath, options) {
234
+ const filePath = assertInsideRoot(root, path.join(root, relativeFilePath));
235
+ const content = await fs$1.readFile(filePath, "utf8");
236
+ const selected = sliceLines(content, options?.startLine, options?.endLine);
237
+ const maxChars = options?.maxChars ?? DEFAULT_MAX_FILE_CHARS;
238
+ const clipped = selected.length > maxChars ? `${selected.slice(0, maxChars)}
239
+
240
+ [truncated: ${selected.length - maxChars} chars omitted]` : selected;
241
+ return {
242
+ filePath,
243
+ content: clipped
244
+ };
245
+ }
246
+ function collectMatchesFromText(query, relativeFilePath, content, maxResults, output) {
247
+ const lines = content.split("\n");
248
+ for (let index = 0; index < lines.length; index += 1) {
249
+ if (output.length >= maxResults) {
250
+ return;
251
+ }
252
+ const lineText = lines[index] ?? "";
253
+ const column = lineText.toLowerCase().indexOf(query.toLowerCase());
254
+ if (column === -1) {
255
+ continue;
256
+ }
257
+ output.push({
258
+ filePath: relativeFilePath,
259
+ line: index + 1,
260
+ column: column + 1,
261
+ text: lineText.trim()
262
+ });
263
+ }
264
+ }
265
+ async function searchTextInDirectory(root, query, options) {
266
+ const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
267
+ const files = await listFilesInDirectory(root, options?.relativeDirectory ?? ".", Math.max(400, maxResults));
268
+ const matches = [];
269
+ for (const file of files) {
270
+ if (matches.length >= maxResults) {
271
+ break;
272
+ }
273
+ const absolutePath = path.join(root, file);
274
+ let content = "";
275
+ try {
276
+ content = await fs$1.readFile(absolutePath, "utf8");
277
+ } catch {
278
+ continue;
279
+ }
280
+ collectMatchesFromText(query, file, content, maxResults, matches);
281
+ }
282
+ return matches;
283
+ }
284
+
285
+ function formatJson(value) {
286
+ return JSON.stringify(value, null, 2);
287
+ }
288
+ function normalizeErrorMessage(error) {
289
+ if (error instanceof Error) {
290
+ return error.message;
291
+ }
292
+ return String(error);
293
+ }
294
+ function toToolResult(data, text) {
295
+ return {
296
+ content: [{
297
+ type: "text",
298
+ text: text ?? formatJson(data)
299
+ }],
300
+ structuredContent: {
301
+ result: data
302
+ }
303
+ };
304
+ }
305
+ function toToolError(error) {
306
+ const message = normalizeErrorMessage(error);
307
+ return {
308
+ isError: true,
16
309
  content: [{
17
310
  type: "text",
18
- text: String(weightKg / (heightM * heightM))
311
+ text: message
19
312
  }]
20
- })
21
- );
22
- server.tool(
23
- "fetch-weather",
24
- { city: z.string() },
25
- async ({ city }) => {
26
- const response = await fetch(`https://api.weather.com/${city}`);
27
- const data = await response.text();
313
+ };
314
+ }
315
+
316
+ const packageIds = Object.keys(EXPOSED_PACKAGES);
317
+ const packageIdSchema = z.enum(packageIds);
318
+ function resolvePackageRoot(workspaceRoot, packageId) {
319
+ return assertInsideRoot(workspaceRoot, path.join(workspaceRoot, EXPOSED_PACKAGES[packageId].relativePath));
320
+ }
321
+ function toDocsUri(packageId, fileName) {
322
+ return `weapp-vite://docs/${packageId}/${fileName}`;
323
+ }
324
+ async function readTextFile(filePath) {
325
+ return fs$1.readFile(filePath, "utf8");
326
+ }
327
+ async function createWeappViteMcpServer(options) {
328
+ const workspaceRoot = resolveWorkspaceRoot(options?.workspaceRoot);
329
+ const server = new McpServer({
330
+ name: MCP_SERVER_NAME,
331
+ version: MCP_SERVER_VERSION
332
+ });
333
+ server.registerTool("workspace_catalog", {
334
+ title: "Workspace Catalog",
335
+ description: "\u8BFB\u53D6 weapp-vite / wevu \u76F8\u5173\u5305\u76EE\u5F55\u4E0E\u811A\u672C\u80FD\u529B\u6E05\u5355"
336
+ }, async () => {
337
+ try {
338
+ const catalog2 = await loadExposedCatalog(workspaceRoot);
339
+ return toToolResult({
340
+ workspaceRoot,
341
+ packages: catalog2
342
+ });
343
+ } catch (error) {
344
+ return toToolError(error);
345
+ }
346
+ });
347
+ server.registerTool("list_source_files", {
348
+ title: "List Source Files",
349
+ description: "\u5217\u51FA weapp-vite / wevu \u5305\u4E0B\u6307\u5B9A\u76EE\u5F55\u6587\u4EF6\u5217\u8868",
350
+ inputSchema: {
351
+ packageId: packageIdSchema,
352
+ directory: z.string().optional().describe("\u5305\u5185\u76F8\u5BF9\u76EE\u5F55\uFF0C\u9ED8\u8BA4 src"),
353
+ maxResults: z.number().int().positive().max(2e3).optional()
354
+ }
355
+ }, async ({ packageId, directory, maxResults }) => {
356
+ try {
357
+ const packageRoot = resolvePackageRoot(workspaceRoot, packageId);
358
+ const files = await listFilesInDirectory(packageRoot, directory ?? "src", maxResults ?? DEFAULT_MAX_RESULTS);
359
+ return toToolResult({
360
+ packageId,
361
+ directory: directory ?? "src",
362
+ count: files.length,
363
+ files
364
+ });
365
+ } catch (error) {
366
+ return toToolError(error);
367
+ }
368
+ });
369
+ server.registerTool("read_source_file", {
370
+ title: "Read Source File",
371
+ description: "\u8BFB\u53D6 weapp-vite / wevu \u5305\u5185\u6E90\u7801\u6587\u4EF6\uFF0C\u652F\u6301\u884C\u533A\u95F4\u88C1\u526A",
372
+ inputSchema: {
373
+ packageId: packageIdSchema,
374
+ filePath: z.string().describe("\u5305\u5185\u76F8\u5BF9\u6587\u4EF6\u8DEF\u5F84"),
375
+ startLine: z.number().int().positive().optional(),
376
+ endLine: z.number().int().positive().optional(),
377
+ maxChars: z.number().int().positive().max(2e5).optional()
378
+ }
379
+ }, async ({ packageId, filePath, startLine, endLine, maxChars }) => {
380
+ try {
381
+ const packageRoot = resolvePackageRoot(workspaceRoot, packageId);
382
+ const { filePath: absolutePath, content } = await readFileContent(packageRoot, filePath, {
383
+ startLine,
384
+ endLine,
385
+ maxChars: maxChars ?? DEFAULT_MAX_FILE_CHARS
386
+ });
387
+ return toToolResult({
388
+ packageId,
389
+ filePath,
390
+ absolutePath,
391
+ startLine: startLine ?? null,
392
+ endLine: endLine ?? null,
393
+ content
394
+ }, content);
395
+ } catch (error) {
396
+ return toToolError(error);
397
+ }
398
+ });
399
+ server.registerTool("search_source_code", {
400
+ title: "Search Source Code",
401
+ description: "\u5728 weapp-vite / wevu \u4EE3\u7801\u4E2D\u641C\u7D22\u5173\u952E\u8BCD",
402
+ inputSchema: {
403
+ query: z.string().min(1),
404
+ packageId: packageIdSchema.optional(),
405
+ directory: z.string().optional(),
406
+ maxResults: z.number().int().positive().max(2e3).optional()
407
+ }
408
+ }, async ({ query, packageId, directory, maxResults }) => {
409
+ try {
410
+ const targetPackageIds = packageId ? [packageId] : packageIds;
411
+ const allMatches = [];
412
+ const safeMax = maxResults ?? DEFAULT_MAX_RESULTS;
413
+ for (const id of targetPackageIds) {
414
+ if (allMatches.length >= safeMax) {
415
+ break;
416
+ }
417
+ const packageRoot = resolvePackageRoot(workspaceRoot, id);
418
+ const matches = await searchTextInDirectory(packageRoot, query, {
419
+ relativeDirectory: directory ?? "src",
420
+ maxResults: safeMax - allMatches.length
421
+ });
422
+ allMatches.push(...matches.map((match) => ({
423
+ packageId: id,
424
+ ...match
425
+ })));
426
+ }
427
+ return toToolResult({
428
+ query,
429
+ total: allMatches.length,
430
+ matches: allMatches
431
+ });
432
+ } catch (error) {
433
+ return toToolError(error);
434
+ }
435
+ });
436
+ server.registerTool("run_package_script", {
437
+ title: "Run Package Script",
438
+ description: "\u5728 weapp-vite / wevu \u5305\u76EE\u5F55\u6267\u884C pnpm script",
439
+ inputSchema: {
440
+ packageId: packageIdSchema,
441
+ script: z.string().min(1),
442
+ args: z.array(z.string()).optional(),
443
+ timeoutMs: z.number().int().positive().max(9e5).optional()
444
+ }
445
+ }, async ({ packageId, script, args, timeoutMs }) => {
446
+ try {
447
+ const cwdRelative = EXPOSED_PACKAGES[packageId].relativePath;
448
+ const result = await runCommand(workspaceRoot, "pnpm", ["run", script, ...args ?? []], {
449
+ cwdRelative,
450
+ timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS
451
+ });
452
+ return toToolResult(result);
453
+ } catch (error) {
454
+ return toToolError(error);
455
+ }
456
+ });
457
+ server.registerTool("run_weapp_vite_cli", {
458
+ title: "Run weapp-vite CLI",
459
+ description: "\u6267\u884C weapp-vite CLI\uFF08build/dev/open/analyze \u7B49\uFF09",
460
+ inputSchema: {
461
+ subCommand: z.string().min(1),
462
+ projectPath: z.string().optional().describe("\u76F8\u5BF9 workspace \u6839\u8DEF\u5F84\uFF0C\u5982 e2e-apps/auto-routes-define-app-json"),
463
+ platform: z.string().optional(),
464
+ args: z.array(z.string()).optional(),
465
+ timeoutMs: z.number().int().positive().max(9e5).optional()
466
+ }
467
+ }, async ({ subCommand, projectPath, platform, args, timeoutMs }) => {
468
+ try {
469
+ const cliPath = path.join("packages", "weapp-vite", "bin", "weapp-vite.js");
470
+ const finalArgs = [cliPath, subCommand];
471
+ if (projectPath) {
472
+ finalArgs.push(resolveSubPath(workspaceRoot, projectPath));
473
+ }
474
+ if (platform) {
475
+ finalArgs.push("--platform", platform);
476
+ }
477
+ if (Array.isArray(args) && args.length > 0) {
478
+ finalArgs.push(...args);
479
+ }
480
+ const result = await runCommand(workspaceRoot, "node", finalArgs, {
481
+ timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS
482
+ });
483
+ return toToolResult(result);
484
+ } catch (error) {
485
+ return toToolError(error);
486
+ }
487
+ });
488
+ server.registerTool("run_repo_command", {
489
+ title: "Run Repo Command",
490
+ description: "\u6267\u884C\u4ED3\u5E93\u7EA7\u547D\u4EE4\uFF08\u652F\u6301 pnpm/node/git/rg\uFF09",
491
+ inputSchema: {
492
+ command: z.enum(["pnpm", "node", "git", "rg"]),
493
+ args: z.array(z.string()).optional(),
494
+ cwdRelative: z.string().optional(),
495
+ timeoutMs: z.number().int().positive().max(9e5).optional()
496
+ }
497
+ }, async ({ command, args, cwdRelative, timeoutMs }) => {
498
+ try {
499
+ const result = await runCommand(workspaceRoot, command, args ?? [], {
500
+ cwdRelative,
501
+ timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS
502
+ });
503
+ return toToolResult(result);
504
+ } catch (error) {
505
+ return toToolError(error);
506
+ }
507
+ });
508
+ server.registerPrompt("plan-weapp-vite-change", {
509
+ title: "Plan weapp-vite Change",
510
+ description: "\u6839\u636E\u53D8\u66F4\u76EE\u6807\u751F\u6210 weapp-vite / wevu \u4FEE\u6539\u8BA1\u5212\u63D0\u793A\u8BCD",
511
+ argsSchema: {
512
+ objective: z.string().min(1),
513
+ focusPackage: packageIdSchema.optional()
514
+ }
515
+ }, async ({ objective, focusPackage }) => {
516
+ const targets = focusPackage ? [focusPackage] : packageIds;
517
+ return {
518
+ messages: [
519
+ {
520
+ role: "user",
521
+ content: {
522
+ type: "text",
523
+ text: [
524
+ "\u4F60\u662F weapp-vite monorepo \u7EF4\u62A4\u8005\uFF0C\u8BF7\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u6539\u9020\u8BA1\u5212\u3002",
525
+ `\u76EE\u6807\uFF1A${objective}`,
526
+ `\u805A\u7126\u5305\uFF1A${targets.join(", ")}`,
527
+ "\u8BF7\u5305\u542B\uFF1A\u5F71\u54CD\u9762\u3001\u98CE\u9669\u70B9\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u56DE\u6EDA\u7B56\u7565\u3002"
528
+ ].join("\n")
529
+ }
530
+ }
531
+ ]
532
+ };
533
+ });
534
+ server.registerPrompt("debug-wevu-runtime", {
535
+ title: "Debug wevu Runtime",
536
+ description: "\u7528\u4E8E\u5B9A\u4F4D wevu runtime \u751F\u547D\u5468\u671F/\u54CD\u5E94\u5F0F\u95EE\u9898\u7684\u6807\u51C6\u63D0\u793A\u8BCD",
537
+ argsSchema: {
538
+ symptom: z.string().min(1)
539
+ }
540
+ }, async ({ symptom }) => {
28
541
  return {
29
- content: [{ type: "text", text: data }]
542
+ messages: [
543
+ {
544
+ role: "user",
545
+ content: {
546
+ type: "text",
547
+ text: [
548
+ "\u8BF7\u57FA\u4E8E wevu runtime \u4EE3\u7801\u8DEF\u5F84\u8FDB\u884C\u5206\u5C42\u6392\u67E5\uFF1A",
549
+ "1. \u590D\u73B0\u573A\u666F\u4E0E\u6700\u5C0F\u6837\u4F8B",
550
+ "2. \u751F\u547D\u5468\u671F\u94A9\u5B50\u89E6\u53D1\u94FE",
551
+ "3. \u54CD\u5E94\u5F0F\u4E0E setData \u5DEE\u91CF\u540C\u6B65\u94FE",
552
+ "4. \u5355\u6D4B\u4E0E e2e \u56DE\u5F52\u8865\u4E01",
553
+ `\u73B0\u8C61\uFF1A${symptom}`
554
+ ].join("\n")
555
+ }
556
+ }
557
+ ]
30
558
  };
559
+ });
560
+ server.registerResource("workspace-catalog", "weapp-vite://workspace/catalog", {
561
+ title: "Workspace Catalog",
562
+ description: "weapp-vite / wevu \u5305\u76EE\u5F55\u3001\u7248\u672C\u548C\u811A\u672C\u5217\u8868",
563
+ mimeType: "application/json"
564
+ }, async () => {
565
+ const catalog2 = await loadExposedCatalog(workspaceRoot);
566
+ const text = JSON.stringify({ workspaceRoot, packages: catalog2 }, null, 2);
567
+ return {
568
+ contents: [{
569
+ uri: "weapp-vite://workspace/catalog",
570
+ mimeType: "application/json",
571
+ text
572
+ }]
573
+ };
574
+ });
575
+ const catalog = await loadExposedCatalog(workspaceRoot);
576
+ for (const summary of catalog) {
577
+ if (summary.docs.readme) {
578
+ const uri = toDocsUri(summary.id, "README.md");
579
+ server.registerResource(`docs-${summary.id}-readme`, uri, {
580
+ title: `${summary.id} README`,
581
+ mimeType: "text/markdown"
582
+ }, async () => {
583
+ const text = await readTextFile(summary.docs.readme);
584
+ return {
585
+ contents: [{ uri, mimeType: "text/markdown", text }]
586
+ };
587
+ });
588
+ }
589
+ if (summary.docs.changelog) {
590
+ const uri = toDocsUri(summary.id, "CHANGELOG.md");
591
+ server.registerResource(`docs-${summary.id}-changelog`, uri, {
592
+ title: `${summary.id} CHANGELOG`,
593
+ mimeType: "text/markdown"
594
+ }, async () => {
595
+ const text = await readTextFile(summary.docs.changelog);
596
+ return {
597
+ contents: [{ uri, mimeType: "text/markdown", text }]
598
+ };
599
+ });
600
+ }
31
601
  }
32
- );
33
- const transport = new StdioServerTransport();
34
- async function main() {
602
+ const sourceTemplate = new ResourceTemplate("weapp-vite://source/{package}?path={path}", {
603
+ list: void 0,
604
+ complete: {
605
+ package: () => packageIds
606
+ }
607
+ });
608
+ server.registerResource("source-template", sourceTemplate, {
609
+ title: "Source Template",
610
+ description: "\u8BFB\u53D6 weapp-vite / wevu \u4EFB\u610F\u6E90\u7801\u6587\u4EF6\uFF08\u901A\u8FC7 package + path \u53C2\u6570\uFF09",
611
+ mimeType: "text/plain"
612
+ }, async (uri, variables) => {
613
+ try {
614
+ const packageId = String(variables.package ?? "");
615
+ if (!packageIds.includes(packageId)) {
616
+ throw new Error(`\u672A\u77E5 package\uFF1A${packageId}`);
617
+ }
618
+ const relativePath = decodeURIComponent(String(variables.path ?? ""));
619
+ const packageRoot = resolvePackageRoot(workspaceRoot, packageId);
620
+ const { content } = await readFileContent(packageRoot, relativePath, {
621
+ maxChars: DEFAULT_MAX_FILE_CHARS
622
+ });
623
+ return {
624
+ contents: [{
625
+ uri: uri.toString(),
626
+ mimeType: "text/plain",
627
+ text: content
628
+ }]
629
+ };
630
+ } catch (error) {
631
+ return {
632
+ contents: [{
633
+ uri: uri.toString(),
634
+ mimeType: "text/plain",
635
+ text: `[resource-error] ${normalizeErrorMessage(error)}`
636
+ }]
637
+ };
638
+ }
639
+ });
640
+ return {
641
+ server,
642
+ workspaceRoot
643
+ };
644
+ }
645
+
646
+ async function startStdioServer() {
647
+ const { server } = await createWeappViteMcpServer();
648
+ const transport = new StdioServerTransport();
35
649
  await server.connect(transport);
36
650
  }
37
- main();
651
+ function isDirectExecution() {
652
+ const entry = process.argv[1];
653
+ if (!entry) {
654
+ return false;
655
+ }
656
+ return fileURLToPath(import.meta.url) === entry;
657
+ }
658
+ if (isDirectExecution()) {
659
+ startStdioServer().catch((error) => {
660
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
661
+ process.stderr.write(`[mcp] server start failed
662
+ ${message}
663
+ `);
664
+ process.exitCode = 1;
665
+ });
666
+ }
667
+
668
+ export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, assertInsideRoot, createWeappViteMcpServer, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, toToolError, toToolResult };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weapp-vite/mcp",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "description": "mcp",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "ISC",
@@ -31,8 +31,8 @@
31
31
  "node": "^20.19.0 || >=22.12.0"
32
32
  },
33
33
  "dependencies": {
34
- "@modelcontextprotocol/sdk": "^1.25.2",
35
- "zod": "^4.3.5"
34
+ "@modelcontextprotocol/sdk": "^1.27.1",
35
+ "zod": "^4.3.6"
36
36
  },
37
37
  "scripts": {
38
38
  "dev": "unbuild --stub",