@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
|
@@ -1,56 +1,279 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { spawnSync } from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { after, before, test } from 'node:test';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
11
|
+
import {
|
|
12
|
+
MAX_IMAGE_BYTES,
|
|
13
|
+
MAX_REQUEST_BYTES,
|
|
14
|
+
buildDocuments,
|
|
15
|
+
expandMarkdownPaths,
|
|
16
|
+
serializeRequestBody
|
|
17
|
+
} from './documents.js';
|
|
8
18
|
|
|
9
19
|
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00]);
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
let apiServer;
|
|
23
|
+
let client;
|
|
24
|
+
let temporaryDirectory;
|
|
25
|
+
const requests = [];
|
|
26
|
+
|
|
27
|
+
async function readRequestBody(request) {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
30
|
+
return chunks.length > 0 ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function callTool(name, args) {
|
|
34
|
+
return client.callTool({ name, arguments: args });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toolText(result) {
|
|
38
|
+
return result.content.map(item => item.text ?? '').join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
before(async () => {
|
|
42
|
+
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'rag-mcp-bridge-'));
|
|
43
|
+
apiServer = createServer(async (request, response) => {
|
|
44
|
+
const url = new URL(request.url, 'http://localhost');
|
|
45
|
+
requests.push({
|
|
46
|
+
method: request.method,
|
|
47
|
+
path: url.pathname,
|
|
48
|
+
query: Object.fromEntries(url.searchParams),
|
|
49
|
+
body: await readRequestBody(request)
|
|
50
|
+
});
|
|
51
|
+
response.writeHead(200, { 'content-type': 'application/json' });
|
|
52
|
+
response.end(JSON.stringify({ ok: true, endpoint: url.pathname }));
|
|
53
|
+
});
|
|
54
|
+
await new Promise(resolve => apiServer.listen(0, '127.0.0.1', resolve));
|
|
55
|
+
const address = apiServer.address();
|
|
56
|
+
const apiBaseUrl = `http://127.0.0.1:${address.port}/api/knowledge`;
|
|
57
|
+
|
|
58
|
+
client = new Client({ name: 'rag-mcp-bridge-test', version: '1.0.0' });
|
|
13
59
|
const transport = new StdioClientTransport({
|
|
14
60
|
command: process.execPath,
|
|
15
|
-
args: [path.join(directory,
|
|
16
|
-
cwd: path.resolve(directory,
|
|
17
|
-
env: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
args: [path.join(directory, 'index.js')],
|
|
62
|
+
cwd: path.resolve(directory, '../..'),
|
|
63
|
+
env: { RAG_API_BASE_URL: apiBaseUrl },
|
|
64
|
+
stderr: 'pipe'
|
|
65
|
+
});
|
|
66
|
+
await client.connect(transport);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
after(async () => {
|
|
70
|
+
await client?.close();
|
|
71
|
+
await new Promise(resolve => apiServer?.close(resolve));
|
|
72
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('initializes and preserves the existing MCP tool contract', async () => {
|
|
76
|
+
assert.equal(client.getServerVersion()?.name, 'rag-mcp-bridge');
|
|
77
|
+
assert.match(client.getInstructions() ?? '', /retrieve_knowledge/);
|
|
78
|
+
const { tools } = await client.listTools();
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
tools.map(({ name }) => name),
|
|
81
|
+
['add_prd_file', 'update_spec_file', 'retrieve_knowledge']
|
|
82
|
+
);
|
|
83
|
+
assert.equal(tools.find(({ name }) => name === 'retrieve_knowledge')?.annotations?.readOnlyHint, true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('add_prd_file uploads one Unicode Markdown file and its encoded local image', async () => {
|
|
87
|
+
const root = path.join(temporaryDirectory, '单文件 根目录');
|
|
88
|
+
const docs = path.join(root, '页面 文档');
|
|
89
|
+
const attachments = path.join(root, '.attachments');
|
|
90
|
+
await Promise.all([mkdir(docs, { recursive: true }), mkdir(attachments, { recursive: true })]);
|
|
91
|
+
const markdownPath = path.join(docs, '首页 需求.md');
|
|
92
|
+
const imageReference = '../.attachments/%E7%A4%BA%E4%BE%8B%20%E5%9B%BE%E7%89%87.png';
|
|
93
|
+
const markdown = `# 首页\n\n![本地图片][local-shot]\n\n\n\n[local-shot]: ${imageReference}\n`;
|
|
94
|
+
await Promise.all([writeFile(markdownPath, markdown), writeFile(path.join(attachments, '示例 图片.png'), png)]);
|
|
95
|
+
|
|
96
|
+
const result = await callTool('add_prd_file', {
|
|
97
|
+
paths: markdownPath,
|
|
98
|
+
platform: 'app',
|
|
99
|
+
module: 'vehicle-selector',
|
|
100
|
+
page: 'home'
|
|
101
|
+
});
|
|
102
|
+
assert.notEqual(result.isError, true);
|
|
103
|
+
const request = requests.at(-1);
|
|
104
|
+
assert.equal(request.path, '/api/knowledge/add-prd');
|
|
105
|
+
assert.equal('paths' in request.body, false);
|
|
106
|
+
assert.equal(request.body.page, 'home');
|
|
107
|
+
assert.equal(request.body.documents.length, 1);
|
|
108
|
+
assert.deepEqual(request.body.documents[0], {
|
|
109
|
+
docKey: path.resolve(markdownPath),
|
|
110
|
+
fileName: '首页 需求.md',
|
|
111
|
+
markdown,
|
|
112
|
+
assets: [{ path: imageReference, mimeType: 'image/png', base64: png.toString('base64') }]
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('file arrays and recursive directories upload all documents, omit page, and preserve shared assets', async () => {
|
|
117
|
+
const root = path.join(temporaryDirectory, '批量');
|
|
118
|
+
const docs = path.join(root, 'docs');
|
|
119
|
+
const nested = path.join(docs, 'nested');
|
|
120
|
+
const attachments = path.join(root, '.attachments');
|
|
121
|
+
await Promise.all([mkdir(nested, { recursive: true }), mkdir(attachments, { recursive: true })]);
|
|
122
|
+
await writeFile(path.join(attachments, 'shared.png'), png);
|
|
123
|
+
const first = path.join(docs, 'PL.md');
|
|
124
|
+
const second = path.join(docs, 'PN.md');
|
|
125
|
+
const third = path.join(nested, 'PD.md');
|
|
126
|
+
await Promise.all([
|
|
127
|
+
writeFile(first, ''),
|
|
128
|
+
writeFile(second, ''),
|
|
129
|
+
writeFile(third, '# no local image')
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
await callTool('add_prd_file', { paths: [first, second], platform: 'app', page: 'pl' });
|
|
133
|
+
const arrayRequest = requests.at(-1);
|
|
134
|
+
assert.equal(arrayRequest.body.page, undefined);
|
|
135
|
+
assert.equal(arrayRequest.body.documents.length, 2);
|
|
136
|
+
assert.equal(arrayRequest.body.documents[0].assets[0].base64, png.toString('base64'));
|
|
137
|
+
assert.equal(arrayRequest.body.documents[1].assets[0].base64, png.toString('base64'));
|
|
138
|
+
|
|
139
|
+
await callTool('add_prd_file', { paths: docs, platform: 'app', module: 'vehicle-selector' });
|
|
140
|
+
const directoryRequest = requests.at(-1);
|
|
141
|
+
assert.deepEqual(
|
|
142
|
+
directoryRequest.body.documents.map(document => document.fileName),
|
|
143
|
+
['PD.md', 'PL.md', 'PN.md']
|
|
144
|
+
);
|
|
145
|
+
assert.equal(directoryRequest.body.page, undefined);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('update_spec_file uses documents mode and preserves iteration metadata', async () => {
|
|
149
|
+
const specPath = path.join(temporaryDirectory, '迭代 spec.md');
|
|
150
|
+
await writeFile(specPath, '# iteration');
|
|
151
|
+
await callTool('update_spec_file', {
|
|
152
|
+
paths: specPath,
|
|
153
|
+
sprintVersion: 12,
|
|
154
|
+
iterationTag: '2026-S12',
|
|
155
|
+
platform: 'california-web',
|
|
156
|
+
module: 'vehicle-selector',
|
|
157
|
+
page: 'pd'
|
|
158
|
+
});
|
|
159
|
+
const request = requests.at(-1);
|
|
160
|
+
assert.equal(request.path, '/api/knowledge/add-spec');
|
|
161
|
+
assert.equal('paths' in request.body, false);
|
|
162
|
+
assert.equal(request.body.sprintVersion, 12);
|
|
163
|
+
assert.equal(request.body.iterationTag, '2026-S12');
|
|
164
|
+
assert.equal(request.body.page, 'pd');
|
|
165
|
+
assert.equal(request.body.documents[0].markdown, '# iteration');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('retrieve_knowledge remains a GET request with the existing query mapping', async () => {
|
|
169
|
+
await callTool('retrieve_knowledge', {
|
|
170
|
+
query: 'selector requirements',
|
|
171
|
+
platform: 'app',
|
|
172
|
+
module: 'vehicle-selector',
|
|
173
|
+
page: 'home',
|
|
174
|
+
enableStruct: false,
|
|
175
|
+
includeImages: false
|
|
176
|
+
});
|
|
177
|
+
const request = requests.at(-1);
|
|
178
|
+
assert.equal(request.method, 'GET');
|
|
179
|
+
assert.equal(request.path, '/api/knowledge/search');
|
|
180
|
+
assert.deepEqual(request.query, {
|
|
181
|
+
query: 'selector requirements',
|
|
182
|
+
platform: 'app',
|
|
183
|
+
module: 'vehicle-selector',
|
|
184
|
+
page: 'home',
|
|
185
|
+
enableStruct: 'false',
|
|
186
|
+
includeImages: 'false'
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('reports missing images with the Markdown file and referenced path', async () => {
|
|
191
|
+
const markdownPath = path.join(temporaryDirectory, '缺图.md');
|
|
192
|
+
await writeFile(markdownPath, '');
|
|
193
|
+
const result = await callTool('add_prd_file', { paths: markdownPath, platform: 'app' });
|
|
194
|
+
assert.equal(result.isError, true);
|
|
195
|
+
assert.match(toolText(result), /缺图\.md/);
|
|
196
|
+
assert.match(toolText(result), /images\/not-found\.png/);
|
|
197
|
+
assert.match(toolText(result), /不存在/);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('rejects unsafe image inputs', async t => {
|
|
201
|
+
await t.test('unsupported extension', async () => {
|
|
202
|
+
const markdownPath = path.join(temporaryDirectory, '非法类型.md');
|
|
203
|
+
await writeFile(markdownPath, '');
|
|
204
|
+
const result = await callTool('add_prd_file', { paths: markdownPath, platform: 'app' });
|
|
205
|
+
assert.equal(result.isError, true);
|
|
206
|
+
assert.match(toolText(result), /不受支持的图片类型/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await t.test('MIME signature mismatch', async () => {
|
|
210
|
+
const markdownPath = path.join(temporaryDirectory, '伪装图片.md');
|
|
211
|
+
await Promise.all([
|
|
212
|
+
writeFile(markdownPath, ''),
|
|
213
|
+
writeFile(path.join(temporaryDirectory, 'fake.png'), 'secret')
|
|
214
|
+
]);
|
|
215
|
+
const result = await callTool('add_prd_file', { paths: markdownPath, platform: 'app' });
|
|
216
|
+
assert.equal(result.isError, true);
|
|
217
|
+
assert.match(toolText(result), /内容与扩展名不匹配/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await t.test('single image size limit', async () => {
|
|
221
|
+
const markdownPath = path.join(temporaryDirectory, '超大图片.md');
|
|
222
|
+
const oversized = Buffer.alloc(MAX_IMAGE_BYTES + 1);
|
|
223
|
+
png.copy(oversized);
|
|
224
|
+
await Promise.all([
|
|
225
|
+
writeFile(markdownPath, ''),
|
|
226
|
+
writeFile(path.join(temporaryDirectory, 'large.png'), oversized)
|
|
227
|
+
]);
|
|
228
|
+
const result = await callTool('add_prd_file', { paths: markdownPath, platform: 'app' });
|
|
229
|
+
assert.equal(result.isError, true);
|
|
230
|
+
assert.match(toolText(result), /超过单文件限制/);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
await t.test('path traversal outside approved attachment roots', async () => {
|
|
234
|
+
const docs = path.join(temporaryDirectory, 'traversal', 'docs');
|
|
235
|
+
await mkdir(docs, { recursive: true });
|
|
236
|
+
const markdownPath = path.join(docs, '越界.md');
|
|
237
|
+
await writeFile(markdownPath, '');
|
|
238
|
+
const result = await callTool('add_prd_file', { paths: markdownPath, platform: 'app' });
|
|
239
|
+
assert.equal(result.isError, true);
|
|
240
|
+
assert.match(toolText(result), /越界\.md/);
|
|
241
|
+
assert.match(toolText(result), /\.\.\/secret\.png/);
|
|
242
|
+
assert.match(toolText(result), /路径越界/);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('enforces the total request size while packaging and serializing', async () => {
|
|
247
|
+
const markdownPath = path.join(temporaryDirectory, '超大文档.md');
|
|
248
|
+
await writeFile(markdownPath, 'x'.repeat(MAX_REQUEST_BYTES + 1));
|
|
249
|
+
await assert.rejects(() => buildDocuments([markdownPath]), /内容超过单次请求限制/);
|
|
250
|
+
assert.throws(
|
|
251
|
+
() => serializeRequestBody({ documents: [{ markdown: 'x'.repeat(MAX_REQUEST_BYTES) }] }),
|
|
252
|
+
/超过单次限制/
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('expands files, arrays, and recursive directories in stable order', async () => {
|
|
257
|
+
const root = path.join(temporaryDirectory, 'expand');
|
|
258
|
+
const nested = path.join(root, 'nested');
|
|
259
|
+
await mkdir(nested, { recursive: true });
|
|
260
|
+
const a = path.join(root, 'a.md');
|
|
261
|
+
const b = path.join(nested, 'b.md');
|
|
262
|
+
await Promise.all([writeFile(a, '# a'), writeFile(b, '# b'), writeFile(path.join(root, 'ignored.txt'), 'x')]);
|
|
263
|
+
assert.deepEqual(await expandMarkdownPaths(a), [a]);
|
|
264
|
+
assert.deepEqual(await expandMarkdownPaths([b, a]), [a, b]);
|
|
265
|
+
assert.deepEqual(await expandMarkdownPaths(root), [a, b]);
|
|
266
|
+
assert.equal((await buildDocuments([a]))[0].markdown, '# a');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('fails to start when RAG_API_BASE_URL is not configured', () => {
|
|
45
270
|
const environment = { ...process.env };
|
|
46
271
|
delete environment.RAG_API_BASE_URL;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cwd: path.resolve(directory, "../.."),
|
|
272
|
+
const result = spawnSync(process.execPath, [path.join(directory, 'index.js')], {
|
|
273
|
+
cwd: path.resolve(directory, '../..'),
|
|
50
274
|
env: environment,
|
|
51
|
-
encoding:
|
|
275
|
+
encoding: 'utf8'
|
|
52
276
|
});
|
|
53
|
-
|
|
54
277
|
assert.notEqual(result.status, 0);
|
|
55
278
|
assert.match(result.stderr, /RAG_API_BASE_URL is required/);
|
|
56
279
|
});
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "node index.js",
|
|
13
|
-
"lint": "node --check index.js && node --check index.test.js",
|
|
13
|
+
"lint": "node --check index.js && node --check documents.js && node --check index.test.js",
|
|
14
14
|
"test": "node --test index.test.js",
|
|
15
15
|
"verify": "npm run lint && npm test"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
19
|
+
"mdast-util-from-markdown": "2.0.2",
|
|
19
20
|
"zod": "4.4.3"
|
|
20
21
|
}
|
|
21
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autobest-ui/agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Autobest Agent skills/plugins/mcp assets + sync cli",
|
|
6
6
|
"files": [
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
40
40
|
"diff": "9.0.0",
|
|
41
41
|
"fs-extra": "11.3.0",
|
|
42
|
+
"mdast-util-from-markdown": "2.0.2",
|
|
42
43
|
"ws": "8.18.3",
|
|
43
44
|
"zod": "4.4.3"
|
|
44
45
|
},
|