@autobest-ui/agent 1.0.1 → 1.0.3
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 +3 -3
- package/mcp/azurepr-mcp-bridge/azure-devops.js +82 -112
- package/mcp/azurepr-mcp-bridge/index.js +21 -25
- package/mcp/azurepr-mcp-bridge/index.test.js +50 -59
- package/mcp/figma-mcp-bridge/src/tools/index.js +782 -361
- package/mcp/figma-mcp-bridge/src/tools/mutations.js +3566 -2160
- package/mcp/figma-mcp-bridge/src/tools/nodes.js +53 -32
- package/mcp/figma-mcp-bridge/src/tools/pages.js +38 -23
- package/mcp/rag-mcp-bridge/README.md +40 -3
- package/mcp/rag-mcp-bridge/codex-system-prompt.md +1 -1
- package/mcp/rag-mcp-bridge/config.toml.example +1 -1
- package/mcp/rag-mcp-bridge/documents.js +236 -0
- package/mcp/rag-mcp-bridge/index.js +82 -173
- package/mcp/rag-mcp-bridge/index.test.js +267 -44
- package/mcp/rag-mcp-bridge/package.json +2 -1
- package/package.json +2 -1
|
@@ -4,46 +4,42 @@
|
|
|
4
4
|
* RAG MCP bridge for the local PRD knowledge Express API.
|
|
5
5
|
*
|
|
6
6
|
* Responsibilities are deliberately limited to:
|
|
7
|
-
* 1.
|
|
8
|
-
* 2. Forwarding MCP tool calls to the
|
|
7
|
+
* 1. Packaging local Markdown files and referenced images for remote ingestion.
|
|
8
|
+
* 2. Forwarding MCP tool calls to the configured HTTP API.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Requirement parsing, database access, embeddings, and RAG stay in Express.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { z } from "zod";
|
|
13
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { buildDocuments, expandMarkdownPaths, serializeRequestBody } from './documents.js';
|
|
18
17
|
|
|
19
18
|
const DEFAULT_HTTP_TIMEOUT_MS = 120_000;
|
|
20
19
|
|
|
21
20
|
function apiBaseUrlFromEnvironment() {
|
|
22
21
|
const configuredUrl = process.env.RAG_API_BASE_URL?.trim();
|
|
23
22
|
if (!configuredUrl) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
"RAG_API_BASE_URL is required; configure it in the MCP server environment",
|
|
26
|
-
);
|
|
23
|
+
throw new Error('RAG_API_BASE_URL is required; configure it in the MCP server environment');
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
let url;
|
|
30
27
|
try {
|
|
31
28
|
url = new URL(configuredUrl);
|
|
32
29
|
} catch {
|
|
33
|
-
throw new Error(
|
|
30
|
+
throw new Error('RAG_API_BASE_URL must be a valid URL');
|
|
34
31
|
}
|
|
35
|
-
if (url.protocol !==
|
|
36
|
-
throw new Error(
|
|
32
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
33
|
+
throw new Error('RAG_API_BASE_URL must use http or https');
|
|
37
34
|
}
|
|
38
|
-
return url.toString().replace(/\/$/,
|
|
35
|
+
return url.toString().replace(/\/$/, '');
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
function httpTimeoutFromEnvironment() {
|
|
42
|
-
const configuredTimeout =
|
|
43
|
-
process.env.RAG_MCP_HTTP_TIMEOUT_MS ?? String(DEFAULT_HTTP_TIMEOUT_MS);
|
|
39
|
+
const configuredTimeout = process.env.RAG_MCP_HTTP_TIMEOUT_MS ?? String(DEFAULT_HTTP_TIMEOUT_MS);
|
|
44
40
|
const timeout = Number(configuredTimeout);
|
|
45
41
|
if (!Number.isSafeInteger(timeout) || timeout <= 0) {
|
|
46
|
-
throw new Error(
|
|
42
|
+
throw new Error('RAG_MCP_HTTP_TIMEOUT_MS must be a positive integer');
|
|
47
43
|
}
|
|
48
44
|
return timeout;
|
|
49
45
|
}
|
|
@@ -52,92 +48,27 @@ const API_BASE_URL = apiBaseUrlFromEnvironment();
|
|
|
52
48
|
const HTTP_TIMEOUT_MS = httpTimeoutFromEnvironment();
|
|
53
49
|
|
|
54
50
|
const pathsSchema = z
|
|
55
|
-
.union([
|
|
56
|
-
|
|
57
|
-
z.array(z.string().min(1)).min(1),
|
|
58
|
-
])
|
|
59
|
-
.describe("单个Markdown文件、Markdown文件数组或文件夹的绝对路径");
|
|
51
|
+
.union([z.string().min(1), z.array(z.string().min(1)).min(1)])
|
|
52
|
+
.describe('单个Markdown文件、Markdown文件数组或文件夹的绝对路径');
|
|
60
53
|
const platformSchema = z
|
|
61
|
-
.enum([
|
|
62
|
-
.describe(
|
|
63
|
-
const moduleSchema = z
|
|
64
|
-
.string()
|
|
65
|
-
.min(1)
|
|
66
|
-
.optional()
|
|
67
|
-
.describe("可选业务模块标识,例如vehicle-selector");
|
|
54
|
+
.enum(['app', 'california-web', 'shared'])
|
|
55
|
+
.describe('知识所属平台;必须明确指定,防止APP与加州网站需求串库');
|
|
56
|
+
const moduleSchema = z.string().min(1).optional().describe('可选业务模块标识,例如vehicle-selector');
|
|
68
57
|
const pageSchema = z
|
|
69
58
|
.string()
|
|
70
59
|
.min(1)
|
|
71
60
|
.optional()
|
|
72
|
-
.describe(
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
const docKeySchema = z
|
|
76
|
-
.string()
|
|
77
|
-
.min(1)
|
|
78
|
-
.optional()
|
|
79
|
-
.describe("需要限定到单一文档时传knowledge_doc.doc_key绝对路径");
|
|
80
|
-
|
|
81
|
-
/** Recursively collects Markdown files from a directory in stable order. */
|
|
82
|
-
async function scanMarkdownDirectory(directoryPath) {
|
|
83
|
-
const markdownFiles = [];
|
|
84
|
-
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
85
|
-
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
86
|
-
|
|
87
|
-
for (const entry of entries) {
|
|
88
|
-
const entryPath = path.join(directoryPath, entry.name);
|
|
89
|
-
if (entry.isDirectory()) {
|
|
90
|
-
markdownFiles.push(...(await scanMarkdownDirectory(entryPath)));
|
|
91
|
-
} else if (entry.isFile() && path.extname(entry.name).toLowerCase() === ".md") {
|
|
92
|
-
markdownFiles.push(path.resolve(entryPath));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return markdownFiles;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Normalizes all supported path inputs to a unique array of absolute .md files. */
|
|
99
|
-
async function expandMarkdownPaths(pathsInput) {
|
|
100
|
-
const inputs = Array.isArray(pathsInput) ? pathsInput : [pathsInput];
|
|
101
|
-
const markdownFiles = [];
|
|
102
|
-
|
|
103
|
-
for (const input of inputs) {
|
|
104
|
-
if (typeof input !== "string" || !input.trim()) {
|
|
105
|
-
throw new Error("paths中的每一项都必须是非空字符串");
|
|
106
|
-
}
|
|
107
|
-
if (!path.isAbsolute(input.trim())) {
|
|
108
|
-
throw new Error(`必须使用绝对路径: ${input}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const absolutePath = path.resolve(input.trim());
|
|
112
|
-
const pathStat = await stat(absolutePath);
|
|
113
|
-
if (pathStat.isDirectory()) {
|
|
114
|
-
markdownFiles.push(...(await scanMarkdownDirectory(absolutePath)));
|
|
115
|
-
} else if (
|
|
116
|
-
pathStat.isFile() &&
|
|
117
|
-
path.extname(absolutePath).toLowerCase() === ".md"
|
|
118
|
-
) {
|
|
119
|
-
markdownFiles.push(absolutePath);
|
|
120
|
-
} else {
|
|
121
|
-
throw new Error(`路径不是Markdown文件或文件夹: ${absolutePath}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const uniquePaths = [...new Set(markdownFiles)].sort((left, right) =>
|
|
126
|
-
left.localeCompare(right),
|
|
127
|
-
);
|
|
128
|
-
if (uniquePaths.length === 0) {
|
|
129
|
-
throw new Error("指定路径中没有找到任何.md文件");
|
|
130
|
-
}
|
|
131
|
-
return uniquePaths;
|
|
132
|
-
}
|
|
61
|
+
.describe('业务页面键,例如home、pl、pn、pd、name-search;录入多个文件时不要传,由后端按文件名分别推断');
|
|
62
|
+
const docKeySchema = z.string().min(1).optional().describe('需要限定到单一文档时传knowledge_doc.doc_key绝对路径');
|
|
133
63
|
|
|
134
64
|
/** Posts JSON to Express and returns its parsed response body. */
|
|
135
65
|
async function postJson(endpoint, body) {
|
|
66
|
+
const serializedBody = serializeRequestBody(body);
|
|
136
67
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
137
|
-
method:
|
|
138
|
-
headers: {
|
|
139
|
-
body:
|
|
140
|
-
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'content-type': 'application/json' },
|
|
70
|
+
body: serializedBody,
|
|
71
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
141
72
|
});
|
|
142
73
|
const responseText = await response.text();
|
|
143
74
|
let responseBody;
|
|
@@ -148,10 +79,7 @@ async function postJson(endpoint, body) {
|
|
|
148
79
|
}
|
|
149
80
|
|
|
150
81
|
if (!response.ok) {
|
|
151
|
-
const details =
|
|
152
|
-
typeof responseBody === "string"
|
|
153
|
-
? responseBody
|
|
154
|
-
: JSON.stringify(responseBody);
|
|
82
|
+
const details = typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody);
|
|
155
83
|
throw new Error(`Express后端返回HTTP ${response.status}: ${details}`);
|
|
156
84
|
}
|
|
157
85
|
return responseBody;
|
|
@@ -161,12 +89,12 @@ async function postJson(endpoint, body) {
|
|
|
161
89
|
async function getJson(endpoint, query) {
|
|
162
90
|
const url = new URL(`${API_BASE_URL}${endpoint}`);
|
|
163
91
|
for (const [name, value] of Object.entries(query)) {
|
|
164
|
-
if (value === undefined || value === null || value ===
|
|
92
|
+
if (value === undefined || value === null || value === '') continue;
|
|
165
93
|
url.searchParams.set(name, value);
|
|
166
94
|
}
|
|
167
95
|
const response = await fetch(url, {
|
|
168
|
-
method:
|
|
169
|
-
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
96
|
+
method: 'GET',
|
|
97
|
+
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
|
170
98
|
});
|
|
171
99
|
const responseText = await response.text();
|
|
172
100
|
let responseBody;
|
|
@@ -176,10 +104,7 @@ async function getJson(endpoint, query) {
|
|
|
176
104
|
responseBody = responseText;
|
|
177
105
|
}
|
|
178
106
|
if (!response.ok) {
|
|
179
|
-
const details =
|
|
180
|
-
typeof responseBody === "string"
|
|
181
|
-
? responseBody
|
|
182
|
-
: JSON.stringify(responseBody);
|
|
107
|
+
const details = typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody);
|
|
183
108
|
throw new Error(`Express后端返回HTTP ${response.status}: ${details}`);
|
|
184
109
|
}
|
|
185
110
|
return responseBody;
|
|
@@ -189,10 +114,10 @@ function toolSuccess(payload) {
|
|
|
189
114
|
return {
|
|
190
115
|
content: [
|
|
191
116
|
{
|
|
192
|
-
type:
|
|
193
|
-
text: JSON.stringify(payload, null, 2)
|
|
194
|
-
}
|
|
195
|
-
]
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: JSON.stringify(payload, null, 2)
|
|
119
|
+
}
|
|
120
|
+
]
|
|
196
121
|
};
|
|
197
122
|
}
|
|
198
123
|
|
|
@@ -200,154 +125,138 @@ function toolError(error) {
|
|
|
200
125
|
const message = error instanceof Error ? error.message : String(error);
|
|
201
126
|
return {
|
|
202
127
|
isError: true,
|
|
203
|
-
content: [{ type:
|
|
128
|
+
content: [{ type: 'text', text: message }]
|
|
204
129
|
};
|
|
205
130
|
}
|
|
206
131
|
|
|
207
132
|
const server = new McpServer(
|
|
208
|
-
{ name:
|
|
133
|
+
{ name: 'rag-mcp-bridge', version: '1.0.0' },
|
|
209
134
|
{
|
|
210
135
|
instructions:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
136
|
+
'Use retrieve_knowledge before implementing requirement-sensitive code. ' +
|
|
137
|
+
'Every tool call requires an explicit platform: app, california-web, or shared. ' +
|
|
138
|
+
'add_prd_file and update_spec_file accept one file, an array of files, or a directory. ' +
|
|
139
|
+
'Only call update_spec_file after the PR is merged and leader review confirms the spec is effective.'
|
|
140
|
+
}
|
|
216
141
|
);
|
|
217
142
|
|
|
218
143
|
server.registerTool(
|
|
219
|
-
|
|
144
|
+
'add_prd_file',
|
|
220
145
|
{
|
|
221
|
-
title:
|
|
146
|
+
title: '录入或更新PRD基线',
|
|
222
147
|
description:
|
|
223
|
-
|
|
148
|
+
'录入/更新PRD基线。paths支持单个md绝对路径、md绝对路径数组或文件夹绝对路径;文件夹会递归扫描全部.md文件。后端负责解析截图并将旧文档标记为作废。',
|
|
224
149
|
inputSchema: {
|
|
225
150
|
paths: pathsSchema,
|
|
226
151
|
platform: platformSchema,
|
|
227
152
|
module: moduleSchema,
|
|
228
|
-
page: pageSchema
|
|
153
|
+
page: pageSchema
|
|
229
154
|
},
|
|
230
155
|
annotations: {
|
|
231
156
|
readOnlyHint: false,
|
|
232
157
|
destructiveHint: false,
|
|
233
158
|
idempotentHint: false,
|
|
234
|
-
openWorldHint: false
|
|
235
|
-
}
|
|
159
|
+
openWorldHint: false
|
|
160
|
+
}
|
|
236
161
|
},
|
|
237
162
|
async ({ paths, platform, module, page }) => {
|
|
238
163
|
try {
|
|
239
164
|
const expandedPaths = await expandMarkdownPaths(paths);
|
|
165
|
+
const documents = await buildDocuments(expandedPaths);
|
|
240
166
|
return toolSuccess(
|
|
241
|
-
await postJson(
|
|
242
|
-
|
|
167
|
+
await postJson('/add-prd', {
|
|
168
|
+
documents,
|
|
243
169
|
platform,
|
|
244
170
|
module,
|
|
245
|
-
page
|
|
246
|
-
})
|
|
171
|
+
page: documents.length === 1 ? page : undefined
|
|
172
|
+
})
|
|
247
173
|
);
|
|
248
174
|
} catch (error) {
|
|
249
175
|
return toolError(error);
|
|
250
176
|
}
|
|
251
|
-
}
|
|
177
|
+
}
|
|
252
178
|
);
|
|
253
179
|
|
|
254
180
|
server.registerTool(
|
|
255
|
-
|
|
181
|
+
'update_spec_file',
|
|
256
182
|
{
|
|
257
|
-
title:
|
|
183
|
+
title: '录入迭代变更Spec',
|
|
258
184
|
description:
|
|
259
|
-
|
|
185
|
+
'仅在PR合并且leader review确认生效后调用。paths支持单个spec.md绝对路径、路径数组或文件夹;只需传改动spec。sprintVersion必须递增,数值越大需求越新。',
|
|
260
186
|
inputSchema: {
|
|
261
187
|
paths: pathsSchema,
|
|
262
188
|
sprintVersion: z.number().int().positive(),
|
|
263
189
|
iterationTag: z.string().min(1),
|
|
264
190
|
platform: platformSchema,
|
|
265
191
|
module: moduleSchema,
|
|
266
|
-
page: pageSchema
|
|
192
|
+
page: pageSchema
|
|
267
193
|
},
|
|
268
194
|
annotations: {
|
|
269
195
|
readOnlyHint: false,
|
|
270
196
|
destructiveHint: false,
|
|
271
197
|
idempotentHint: false,
|
|
272
|
-
openWorldHint: false
|
|
273
|
-
}
|
|
198
|
+
openWorldHint: false
|
|
199
|
+
}
|
|
274
200
|
},
|
|
275
201
|
async ({ paths, sprintVersion, iterationTag, platform, module, page }) => {
|
|
276
202
|
try {
|
|
277
203
|
const expandedPaths = await expandMarkdownPaths(paths);
|
|
204
|
+
const documents = await buildDocuments(expandedPaths);
|
|
278
205
|
return toolSuccess(
|
|
279
|
-
await postJson(
|
|
280
|
-
|
|
206
|
+
await postJson('/add-spec', {
|
|
207
|
+
documents,
|
|
281
208
|
sprintVersion,
|
|
282
209
|
iterationTag,
|
|
283
210
|
platform,
|
|
284
211
|
module,
|
|
285
|
-
page
|
|
286
|
-
})
|
|
212
|
+
page: documents.length === 1 ? page : undefined
|
|
213
|
+
})
|
|
287
214
|
);
|
|
288
215
|
} catch (error) {
|
|
289
216
|
return toolError(error);
|
|
290
217
|
}
|
|
291
|
-
}
|
|
218
|
+
}
|
|
292
219
|
);
|
|
293
220
|
|
|
294
221
|
server.registerTool(
|
|
295
|
-
|
|
222
|
+
'retrieve_knowledge',
|
|
296
223
|
{
|
|
297
|
-
title:
|
|
224
|
+
title: '检索PRD与迭代Spec',
|
|
298
225
|
description:
|
|
299
|
-
|
|
226
|
+
'Codex编写需求相关代码前调用。后端执行向量+BM25-RRF混合检索并按sprint_version业务排序。enableStruct=true时才生成结构化PRD;includeImages=true时才保留图片并替换为数据库图片URL,两者默认均为false。',
|
|
300
227
|
inputSchema: {
|
|
301
228
|
query: z.string().min(1),
|
|
302
229
|
platform: platformSchema,
|
|
303
|
-
module: moduleSchema.describe(
|
|
304
|
-
|
|
305
|
-
),
|
|
306
|
-
page: pageSchema.describe(
|
|
307
|
-
"可选页面硬过滤,例如首页传home;不传时后端会从明确的查询词自动推断",
|
|
308
|
-
),
|
|
230
|
+
module: moduleSchema.describe('可选业务模块硬过滤,例如vehicle-selector'),
|
|
231
|
+
page: pageSchema.describe('可选页面硬过滤,例如首页传home;不传时后端会从明确的查询词自动推断'),
|
|
309
232
|
docKey: docKeySchema,
|
|
310
|
-
enableStruct: z
|
|
311
|
-
|
|
312
|
-
.default(false)
|
|
313
|
-
.describe("是否调用LLM生成structuredPrd,默认false"),
|
|
314
|
-
includeImages: z
|
|
315
|
-
.boolean()
|
|
316
|
-
.default(false)
|
|
317
|
-
.describe("是否返回可供UI显示的数据库图片URL,默认false"),
|
|
233
|
+
enableStruct: z.boolean().default(false).describe('是否调用LLM生成structuredPrd,默认false'),
|
|
234
|
+
includeImages: z.boolean().default(false).describe('是否返回可供UI显示的数据库图片URL,默认false')
|
|
318
235
|
},
|
|
319
236
|
annotations: {
|
|
320
237
|
readOnlyHint: true,
|
|
321
238
|
destructiveHint: false,
|
|
322
239
|
idempotentHint: true,
|
|
323
|
-
openWorldHint: false
|
|
324
|
-
}
|
|
240
|
+
openWorldHint: false
|
|
241
|
+
}
|
|
325
242
|
},
|
|
326
|
-
async ({
|
|
327
|
-
query,
|
|
328
|
-
platform,
|
|
329
|
-
module,
|
|
330
|
-
page,
|
|
331
|
-
docKey,
|
|
332
|
-
enableStruct = false,
|
|
333
|
-
includeImages = false,
|
|
334
|
-
}) => {
|
|
243
|
+
async ({ query, platform, module, page, docKey, enableStruct = false, includeImages = false }) => {
|
|
335
244
|
try {
|
|
336
245
|
return toolSuccess(
|
|
337
|
-
await getJson(
|
|
246
|
+
await getJson('/search', {
|
|
338
247
|
query: query.trim(),
|
|
339
248
|
platform,
|
|
340
249
|
module,
|
|
341
250
|
page,
|
|
342
251
|
docKey,
|
|
343
252
|
enableStruct: String(enableStruct === true),
|
|
344
|
-
includeImages: String(includeImages === true)
|
|
345
|
-
})
|
|
253
|
+
includeImages: String(includeImages === true)
|
|
254
|
+
})
|
|
346
255
|
);
|
|
347
256
|
} catch (error) {
|
|
348
257
|
return toolError(error);
|
|
349
258
|
}
|
|
350
|
-
}
|
|
259
|
+
}
|
|
351
260
|
);
|
|
352
261
|
|
|
353
262
|
async function main() {
|
|
@@ -355,7 +264,7 @@ async function main() {
|
|
|
355
264
|
await server.connect(transport);
|
|
356
265
|
}
|
|
357
266
|
|
|
358
|
-
main().catch(
|
|
359
|
-
console.error(
|
|
267
|
+
main().catch(error => {
|
|
268
|
+
console.error('Failed to start rag-mcp-bridge:', error);
|
|
360
269
|
process.exitCode = 1;
|
|
361
270
|
});
|