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