@avocadostudio-ai/mcp-server 0.4.0 → 0.5.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/dist/tools/index.js +2 -0
- package/dist/tools/qa.d.ts +3 -0
- package/dist/tools/qa.js +115 -0
- package/package.json +2 -2
package/dist/tools/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { registerHistoryTools } from "./history.js";
|
|
|
8
8
|
import { registerChatTools } from "./chat.js";
|
|
9
9
|
import { registerPreviewTools } from "./preview.js";
|
|
10
10
|
import { registerSessionTools } from "./sessions.js";
|
|
11
|
+
import { registerQaTools } from "./qa.js";
|
|
11
12
|
/**
|
|
12
13
|
* `gate` is optional so a caller that has no capability answer yet — every
|
|
13
14
|
* caller, at startup — still registers the full tool set. The gate hides and
|
|
@@ -25,4 +26,5 @@ export function registerAllTools(server, client, gate) {
|
|
|
25
26
|
registerHistoryTools(server, client);
|
|
26
27
|
registerChatTools(server, client);
|
|
27
28
|
registerPreviewTools(server, client);
|
|
29
|
+
registerQaTools(server, client);
|
|
28
30
|
}
|
package/dist/tools/qa.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAllBlockMeta, panelCoverage, formatPanelCoverage } from "@avocadostudio-ai/shared";
|
|
3
|
+
/*
|
|
4
|
+
* The step that asks whether the editing surface an integration just produced is
|
|
5
|
+
* any good.
|
|
6
|
+
*
|
|
7
|
+
* An agent can finish wiring a site — routes mounted, manifest served, markers
|
|
8
|
+
* emitted, build clean — and hand back a property panel whose list rows read
|
|
9
|
+
* `Item 4`, whose labels come from somebody else's block, and whose polymorphic
|
|
10
|
+
* branches never narrow. Every one of those was visible in the data the whole
|
|
11
|
+
* time; nothing was asking. The build cannot see it, the type checker cannot see
|
|
12
|
+
* it, and `editableCoverage` is about the preview.
|
|
13
|
+
*
|
|
14
|
+
* So this is the QA move: after integrating, before claiming success, ask the
|
|
15
|
+
* site what it will actually look like to edit, and either fix what comes back
|
|
16
|
+
* or report it verbatim.
|
|
17
|
+
*
|
|
18
|
+
* It reads the manifest from the ORCHESTRATOR for the same reason discovery
|
|
19
|
+
* does — that is the process which runs the site's `registerBlocks()` — and
|
|
20
|
+
* compares it against the site's real pages. No browser, no screenshot, no model
|
|
21
|
+
* call.
|
|
22
|
+
*/
|
|
23
|
+
/** The editor's own registry, which is what a colliding type name collides with. */
|
|
24
|
+
function editorBuiltins() {
|
|
25
|
+
return getAllBlockMeta();
|
|
26
|
+
}
|
|
27
|
+
export function registerQaTools(server, client) {
|
|
28
|
+
server.tool("avocado-check-editing-surface", "QA the property panel this site produces: which list rows a person can tell apart, which polymorphic branches actually narrow, which props are in the content but described by nothing, and where block type names collide with Avocado's built-ins. Call this after integrating a site or changing its block schemas, and before reporting the integration as done — a site can build cleanly, serve a valid manifest and still be unintelligible to edit.", {
|
|
29
|
+
slugs: z
|
|
30
|
+
.array(z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Pages to examine. Defaults to every slug the site has."),
|
|
33
|
+
includeBuiltinCollisions: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Report block type names that also exist in the editor's built-in registry with a different shape. Default true; this is usually the root cause of several other findings.")
|
|
37
|
+
}, async ({ slugs, includeBuiltinCollisions = true }) => {
|
|
38
|
+
let manifest;
|
|
39
|
+
try {
|
|
40
|
+
manifest = await client.request("GET", "/blocks/manifest");
|
|
41
|
+
if (!manifest || !Array.isArray(manifest.blocks))
|
|
42
|
+
throw new Error("no blocks array");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
/*
|
|
46
|
+
* Deliberately an error rather than a fallback to this process's
|
|
47
|
+
* registry. Discovery can hedge — a labelled guess still helps an agent
|
|
48
|
+
* plan. A QA check that silently measured the wrong site's blocks would
|
|
49
|
+
* report an all-clear on a panel nobody has looked at, which is worse
|
|
50
|
+
* than not running.
|
|
51
|
+
*/
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Cannot QA the editing surface: the site's block manifest is unreadable at ` +
|
|
56
|
+
`${client.config.orchestratorUrl}/blocks/manifest (${err instanceof Error ? err.message : String(err)}). ` +
|
|
57
|
+
`This check has to read the manifest from the process that renders the site, not from this one.`
|
|
58
|
+
}],
|
|
59
|
+
isError: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let targetSlugs = slugs;
|
|
63
|
+
if (!targetSlugs || targetSlugs.length === 0) {
|
|
64
|
+
try {
|
|
65
|
+
const index = await client.request("GET", "/draft/slugs", {
|
|
66
|
+
query: { session: client.config.session, siteId: client.config.siteId }
|
|
67
|
+
});
|
|
68
|
+
targetSlugs = index.slugs ?? [];
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
targetSlugs = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const pages = [];
|
|
75
|
+
const unreadable = [];
|
|
76
|
+
for (const slug of targetSlugs) {
|
|
77
|
+
try {
|
|
78
|
+
pages.push(await client.getPage(slug));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
unreadable.push(slug);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const report = panelCoverage(manifest, pages, {
|
|
85
|
+
...(includeBuiltinCollisions ? { builtinTypes: editorBuiltins() } : {})
|
|
86
|
+
});
|
|
87
|
+
/*
|
|
88
|
+
* Both shapes on purpose. The formatted report is what a person reads when
|
|
89
|
+
* the agent quotes it; the structured findings are what the agent filters,
|
|
90
|
+
* counts and decides on. Handing back only prose makes an agent re-parse
|
|
91
|
+
* its own tool output, and only JSON makes its final message unreadable.
|
|
92
|
+
*/
|
|
93
|
+
return {
|
|
94
|
+
content: [{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: JSON.stringify({
|
|
97
|
+
pagesExamined: pages.length,
|
|
98
|
+
...(unreadable.length > 0 ? { unreadableSlugs: unreadable } : {}),
|
|
99
|
+
rowsExamined: report.rowsExamined,
|
|
100
|
+
rowsLabelled: report.rowsLabelled,
|
|
101
|
+
findingCount: report.findings.length,
|
|
102
|
+
findings: report.findings,
|
|
103
|
+
unknownBlockTypes: report.unknownBlockTypes,
|
|
104
|
+
report: formatPanelCoverage(report),
|
|
105
|
+
guidance: report.findings.length === 0
|
|
106
|
+
? "No editing-surface findings. The panel can label every list row it was shown."
|
|
107
|
+
: "Fix these before reporting the integration as done, or list the ones you are leaving and why. " +
|
|
108
|
+
"`colliding_type` first: where a site re-registers a built-in name with its own shape, anything " +
|
|
109
|
+
"the manifest does not declare explicitly is taken from the built-in, which usually explains " +
|
|
110
|
+
"several of the findings under it."
|
|
111
|
+
}, null, 2)
|
|
112
|
+
}]
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Avocado Studio MCP server — exposes page/block/discovery tools over the Model Context Protocol.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
43
|
"zod": "^4.3.6",
|
|
44
|
-
"@avocadostudio-ai/shared": "^0.
|
|
44
|
+
"@avocadostudio-ai/shared": "^0.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^22.13.10",
|