@xiaou66/vite-plugin-vue-mcp-next 0.0.2 → 0.0.5
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 +88 -16
- package/dist/index.cjs +351 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +351 -31
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +144 -1
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.js +134 -1
- package/dist/runtime/client.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -16,11 +16,13 @@ var DEFAULT_MASK_HEADERS = [
|
|
|
16
16
|
|
|
17
17
|
// src/constants.ts
|
|
18
18
|
var DEFAULT_MCP_PATH = "/__mcp";
|
|
19
|
+
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
19
20
|
var MCP_TOOL_NAMES = {
|
|
20
21
|
listPages: "list_pages",
|
|
21
22
|
getPageState: "get_page_state",
|
|
22
23
|
getDomTree: "get_dom_tree",
|
|
23
24
|
queryDom: "query_dom",
|
|
25
|
+
takeScreenshot: "take_screenshot",
|
|
24
26
|
getConsoleLogs: "get_console_logs",
|
|
25
27
|
clearConsoleLogs: "clear_console_logs",
|
|
26
28
|
evaluateScript: "evaluate_script",
|
|
@@ -37,6 +39,8 @@ var MCP_TOOL_NAMES = {
|
|
|
37
39
|
};
|
|
38
40
|
var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
|
|
39
41
|
var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
|
|
42
|
+
var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
|
|
43
|
+
var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
|
|
40
44
|
var DEFAULT_MCP_CLIENT_SERVER_NAME = "vue-mcp-next";
|
|
41
45
|
var DEFAULT_OPTIONS = {
|
|
42
46
|
mcpPath: DEFAULT_MCP_PATH,
|
|
@@ -76,6 +80,14 @@ var DEFAULT_OPTIONS = {
|
|
|
76
80
|
},
|
|
77
81
|
console: {
|
|
78
82
|
maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
|
|
83
|
+
},
|
|
84
|
+
screenshot: {
|
|
85
|
+
prefer: "auto",
|
|
86
|
+
maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
|
|
87
|
+
snapdom: {
|
|
88
|
+
options: {},
|
|
89
|
+
plugins: []
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
};
|
|
81
93
|
function mergeMcpClientOptions(cursorConfig, mcpClients) {
|
|
@@ -126,6 +138,21 @@ function mergeOptions(options = {}) {
|
|
|
126
138
|
console: {
|
|
127
139
|
...DEFAULT_OPTIONS.console,
|
|
128
140
|
...options.console
|
|
141
|
+
},
|
|
142
|
+
screenshot: {
|
|
143
|
+
...DEFAULT_OPTIONS.screenshot,
|
|
144
|
+
...options.screenshot,
|
|
145
|
+
snapdom: {
|
|
146
|
+
...DEFAULT_OPTIONS.screenshot.snapdom,
|
|
147
|
+
...options.screenshot?.snapdom,
|
|
148
|
+
options: {
|
|
149
|
+
...DEFAULT_OPTIONS.screenshot.snapdom.options,
|
|
150
|
+
...options.screenshot?.snapdom?.options
|
|
151
|
+
},
|
|
152
|
+
plugins: options.screenshot?.snapdom?.plugins ?? [
|
|
153
|
+
...DEFAULT_OPTIONS.screenshot.snapdom.plugins
|
|
154
|
+
]
|
|
155
|
+
}
|
|
129
156
|
}
|
|
130
157
|
};
|
|
131
158
|
}
|
|
@@ -680,9 +707,225 @@ function getPathname(url) {
|
|
|
680
707
|
}
|
|
681
708
|
}
|
|
682
709
|
|
|
710
|
+
// src/mcp/tools/screenshot.ts
|
|
711
|
+
import { z as z5 } from "zod";
|
|
712
|
+
|
|
713
|
+
// src/cdp/cdpScreenshot.ts
|
|
714
|
+
async function cdpCaptureScreenshot(options) {
|
|
715
|
+
if (options.target === "element") {
|
|
716
|
+
return captureElementScreenshot(options);
|
|
717
|
+
}
|
|
718
|
+
if (options.target === "fullPage") {
|
|
719
|
+
return captureFullPageScreenshot(options);
|
|
720
|
+
}
|
|
721
|
+
return captureViewportScreenshot(options);
|
|
722
|
+
}
|
|
723
|
+
async function captureViewportScreenshot(options) {
|
|
724
|
+
const metrics = await options.client.Page.getLayoutMetrics();
|
|
725
|
+
const width = Math.ceil(metrics.cssLayoutViewport.clientWidth);
|
|
726
|
+
const height = Math.ceil(metrics.cssLayoutViewport.clientHeight);
|
|
727
|
+
const result = await options.client.Page.captureScreenshot(
|
|
728
|
+
omitUndefined({
|
|
729
|
+
format: options.format,
|
|
730
|
+
quality: createQuality(options),
|
|
731
|
+
captureBeyondViewport: false
|
|
732
|
+
})
|
|
733
|
+
);
|
|
734
|
+
return { data: result.data, width, height };
|
|
735
|
+
}
|
|
736
|
+
async function captureFullPageScreenshot(options) {
|
|
737
|
+
const metrics = await options.client.Page.getLayoutMetrics();
|
|
738
|
+
const width = Math.ceil(metrics.cssContentSize.width);
|
|
739
|
+
const height = Math.ceil(metrics.cssContentSize.height);
|
|
740
|
+
const result = await options.client.Page.captureScreenshot(
|
|
741
|
+
omitUndefined({
|
|
742
|
+
format: options.format,
|
|
743
|
+
quality: createQuality(options),
|
|
744
|
+
captureBeyondViewport: true,
|
|
745
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
746
|
+
})
|
|
747
|
+
);
|
|
748
|
+
return { data: result.data, width, height };
|
|
749
|
+
}
|
|
750
|
+
async function captureElementScreenshot(options) {
|
|
751
|
+
if (!options.selector) {
|
|
752
|
+
throw new Error("selector is required when target is element");
|
|
753
|
+
}
|
|
754
|
+
const rect = await getElementRect(options.client, options.selector);
|
|
755
|
+
const result = await options.client.Page.captureScreenshot(
|
|
756
|
+
omitUndefined({
|
|
757
|
+
format: options.format,
|
|
758
|
+
quality: createQuality(options),
|
|
759
|
+
captureBeyondViewport: true,
|
|
760
|
+
clip: {
|
|
761
|
+
x: rect.x,
|
|
762
|
+
y: rect.y,
|
|
763
|
+
width: rect.width,
|
|
764
|
+
height: rect.height,
|
|
765
|
+
scale: 1
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
);
|
|
769
|
+
return { data: result.data, width: rect.width, height: rect.height };
|
|
770
|
+
}
|
|
771
|
+
async function getElementRect(client, selector) {
|
|
772
|
+
const result = await client.Runtime.evaluate({
|
|
773
|
+
expression: createElementRectExpression(selector),
|
|
774
|
+
awaitPromise: true,
|
|
775
|
+
returnByValue: true
|
|
776
|
+
});
|
|
777
|
+
if (result.exceptionDetails) {
|
|
778
|
+
throw new Error(result.exceptionDetails.text || "element query failed");
|
|
779
|
+
}
|
|
780
|
+
const value = result.result.value;
|
|
781
|
+
if (!isElementRect(value)) {
|
|
782
|
+
throw new Error(`element not found: ${selector}`);
|
|
783
|
+
}
|
|
784
|
+
return value;
|
|
785
|
+
}
|
|
786
|
+
function createElementRectExpression(selector) {
|
|
787
|
+
return `(() => {
|
|
788
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
789
|
+
if (!el) return null;
|
|
790
|
+
const rect = el.getBoundingClientRect();
|
|
791
|
+
return {
|
|
792
|
+
x: rect.x + window.scrollX,
|
|
793
|
+
y: rect.y + window.scrollY,
|
|
794
|
+
width: rect.width,
|
|
795
|
+
height: rect.height
|
|
796
|
+
};
|
|
797
|
+
})()`;
|
|
798
|
+
}
|
|
799
|
+
function createQuality(options) {
|
|
800
|
+
return options.format === "png" ? void 0 : options.quality;
|
|
801
|
+
}
|
|
802
|
+
function omitUndefined(value) {
|
|
803
|
+
return Object.fromEntries(
|
|
804
|
+
Object.entries(value).filter(([, item]) => item !== void 0)
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
function isElementRect(value) {
|
|
808
|
+
if (!value || typeof value !== "object") {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
const rect = value;
|
|
812
|
+
return typeof rect.x === "number" && typeof rect.y === "number" && typeof rect.width === "number" && rect.width > 0 && typeof rect.height === "number" && rect.height > 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/mcp/tools/screenshot.ts
|
|
816
|
+
var DEFAULT_SCREENSHOT_TARGET = "viewport";
|
|
817
|
+
var DEFAULT_SCREENSHOT_FORMAT = "png";
|
|
818
|
+
var screenshotInputSchema = {
|
|
819
|
+
pageId: z5.string().optional(),
|
|
820
|
+
target: z5.enum(["viewport", "fullPage", "element"]).optional(),
|
|
821
|
+
selector: z5.string().optional(),
|
|
822
|
+
format: z5.enum(["png", "jpeg", "webp"]).optional(),
|
|
823
|
+
prefer: z5.enum(["auto", "cdp", "runtime"]).optional(),
|
|
824
|
+
quality: z5.number().optional(),
|
|
825
|
+
scale: z5.number().optional(),
|
|
826
|
+
snapdom: z5.record(z5.string(), z5.unknown()).optional()
|
|
827
|
+
};
|
|
828
|
+
function registerScreenshotTools(server, ctx) {
|
|
829
|
+
server.registerTool(
|
|
830
|
+
MCP_TOOL_NAMES.takeScreenshot,
|
|
831
|
+
{
|
|
832
|
+
description: "Take a page screenshot using CDP or snapdom fallback.",
|
|
833
|
+
inputSchema: screenshotInputSchema
|
|
834
|
+
},
|
|
835
|
+
async (input) => handleTakeScreenshot(ctx, input)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
async function handleTakeScreenshot(ctx, input) {
|
|
839
|
+
const target = input.target ?? DEFAULT_SCREENSHOT_TARGET;
|
|
840
|
+
const format = input.format ?? DEFAULT_SCREENSHOT_FORMAT;
|
|
841
|
+
const prefer = input.prefer ?? ctx.options.screenshot.prefer;
|
|
842
|
+
if (target === "element" && !input.selector) {
|
|
843
|
+
return createToolError("selector is required when target is element");
|
|
844
|
+
}
|
|
845
|
+
if (prefer !== "runtime") {
|
|
846
|
+
const cdp = await connectCdpForPage(ctx, input.pageId);
|
|
847
|
+
if (cdp) {
|
|
848
|
+
try {
|
|
849
|
+
const screenshot = await cdpCaptureScreenshot({
|
|
850
|
+
client: cdp.client,
|
|
851
|
+
target,
|
|
852
|
+
selector: input.selector,
|
|
853
|
+
format,
|
|
854
|
+
quality: input.quality
|
|
855
|
+
});
|
|
856
|
+
return createScreenshotResponse(ctx, {
|
|
857
|
+
source: "cdp",
|
|
858
|
+
target,
|
|
859
|
+
format,
|
|
860
|
+
data: screenshot.data,
|
|
861
|
+
width: screenshot.width,
|
|
862
|
+
height: screenshot.height,
|
|
863
|
+
mimeType: createMimeType(format),
|
|
864
|
+
byteLength: getBase64ByteLength(screenshot.data)
|
|
865
|
+
});
|
|
866
|
+
} finally {
|
|
867
|
+
await closeCdpClient(cdp.client);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (prefer === "cdp") {
|
|
871
|
+
return createToolError("CDP screenshot is unavailable");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return createRuntimeScreenshot(ctx, input, { target, format });
|
|
875
|
+
}
|
|
876
|
+
async function createRuntimeScreenshot(ctx, input, normalized) {
|
|
877
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
878
|
+
void ctx.rpcServer?.takeScreenshot({
|
|
879
|
+
event,
|
|
880
|
+
target: normalized.target,
|
|
881
|
+
selector: input.selector,
|
|
882
|
+
format: normalized.format,
|
|
883
|
+
quality: input.quality,
|
|
884
|
+
scale: input.scale,
|
|
885
|
+
snapdom: {
|
|
886
|
+
...ctx.options.screenshot.snapdom,
|
|
887
|
+
options: {
|
|
888
|
+
...ctx.options.screenshot.snapdom.options,
|
|
889
|
+
...input.snapdom
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
if (isScreenshotTooLarge(ctx, result)) {
|
|
895
|
+
return createToolError(
|
|
896
|
+
`screenshot is too large: ${String(result.byteLength)} bytes`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
if (!isPlainRecord(result)) {
|
|
900
|
+
return createToolError("runtime screenshot returned an invalid response");
|
|
901
|
+
}
|
|
902
|
+
return createToolResponse(result);
|
|
903
|
+
}
|
|
904
|
+
function createScreenshotResponse(ctx, result) {
|
|
905
|
+
if (result.byteLength > ctx.options.screenshot.maxBytes) {
|
|
906
|
+
return createToolError(
|
|
907
|
+
`screenshot is too large: ${String(result.byteLength)} bytes`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
return createToolResponse(result);
|
|
911
|
+
}
|
|
912
|
+
function isScreenshotTooLarge(ctx, result) {
|
|
913
|
+
return isPlainRecord(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
|
|
914
|
+
}
|
|
915
|
+
function isPlainRecord(value) {
|
|
916
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
917
|
+
}
|
|
918
|
+
function createMimeType(format) {
|
|
919
|
+
return `image/${format}`;
|
|
920
|
+
}
|
|
921
|
+
function getBase64ByteLength(data) {
|
|
922
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
923
|
+
return Math.ceil(data.length * 3 / 4) - padding;
|
|
924
|
+
}
|
|
925
|
+
|
|
683
926
|
// src/mcp/tools/vue.ts
|
|
684
927
|
import { nanoid as nanoid2 } from "nanoid";
|
|
685
|
-
import { z as
|
|
928
|
+
import { z as z6 } from "zod";
|
|
686
929
|
function registerVueTools(server, ctx) {
|
|
687
930
|
server.registerTool(
|
|
688
931
|
MCP_TOOL_NAMES.getComponentTree,
|
|
@@ -695,7 +938,7 @@ function registerVueTools(server, ctx) {
|
|
|
695
938
|
MCP_TOOL_NAMES.getComponentState,
|
|
696
939
|
{
|
|
697
940
|
description: "Get Vue component state.",
|
|
698
|
-
inputSchema: { componentName:
|
|
941
|
+
inputSchema: { componentName: z6.string() }
|
|
699
942
|
},
|
|
700
943
|
async ({ componentName }) => requestVueData(ctx, (event) => {
|
|
701
944
|
void ctx.rpcServer?.getInspectorState({ event, componentName });
|
|
@@ -706,10 +949,10 @@ function registerVueTools(server, ctx) {
|
|
|
706
949
|
{
|
|
707
950
|
description: "Edit Vue component state.",
|
|
708
951
|
inputSchema: {
|
|
709
|
-
componentName:
|
|
710
|
-
path:
|
|
711
|
-
value:
|
|
712
|
-
valueType:
|
|
952
|
+
componentName: z6.string(),
|
|
953
|
+
path: z6.array(z6.string()),
|
|
954
|
+
value: z6.string(),
|
|
955
|
+
valueType: z6.enum(["string", "number", "boolean", "object", "array"])
|
|
713
956
|
}
|
|
714
957
|
},
|
|
715
958
|
({ componentName, path: path5, value, valueType }) => {
|
|
@@ -729,7 +972,7 @@ function registerVueTools(server, ctx) {
|
|
|
729
972
|
MCP_TOOL_NAMES.highlightComponent,
|
|
730
973
|
{
|
|
731
974
|
description: "Highlight a Vue component.",
|
|
732
|
-
inputSchema: { componentName:
|
|
975
|
+
inputSchema: { componentName: z6.string() }
|
|
733
976
|
},
|
|
734
977
|
({ componentName }) => {
|
|
735
978
|
if (!ctx.rpcServer) {
|
|
@@ -757,7 +1000,7 @@ function registerVueTools(server, ctx) {
|
|
|
757
1000
|
MCP_TOOL_NAMES.getPiniaState,
|
|
758
1001
|
{
|
|
759
1002
|
description: "Get Pinia store state.",
|
|
760
|
-
inputSchema: { storeName:
|
|
1003
|
+
inputSchema: { storeName: z6.string() }
|
|
761
1004
|
},
|
|
762
1005
|
async ({ storeName }) => requestVueData(ctx, (event) => {
|
|
763
1006
|
void ctx.rpcServer?.getPiniaState({ event, storeName });
|
|
@@ -800,6 +1043,7 @@ function createMcpServer(ctx, vite) {
|
|
|
800
1043
|
});
|
|
801
1044
|
registerPageTools(server, ctx, vite);
|
|
802
1045
|
registerDomTools(server, ctx);
|
|
1046
|
+
registerScreenshotTools(server, ctx);
|
|
803
1047
|
registerConsoleTools(server, ctx);
|
|
804
1048
|
registerEvaluateTools(server, ctx);
|
|
805
1049
|
registerNetworkTools(server, ctx);
|
|
@@ -809,18 +1053,39 @@ function createMcpServer(ctx, vite) {
|
|
|
809
1053
|
|
|
810
1054
|
// src/mcp/transport.ts
|
|
811
1055
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
812
|
-
|
|
1056
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
1057
|
+
function setupMcpTransport(base, createServer, vite) {
|
|
813
1058
|
const transports = /* @__PURE__ */ new Map();
|
|
814
1059
|
vite.middlewares.use(`${base}/sse`, (_req, res) => {
|
|
815
1060
|
const transport = new SSEServerTransport(`${base}/messages`, res);
|
|
816
|
-
|
|
1061
|
+
const server = createServer();
|
|
1062
|
+
transports.set(transport.sessionId, { server, transport });
|
|
817
1063
|
res.on("close", () => {
|
|
818
1064
|
transports.delete(transport.sessionId);
|
|
1065
|
+
void server.close();
|
|
819
1066
|
});
|
|
820
1067
|
void server.connect(transport).catch((error) => {
|
|
821
1068
|
res.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
822
1069
|
});
|
|
823
1070
|
});
|
|
1071
|
+
vite.middlewares.use(`${base}/mcp`, (req, res) => {
|
|
1072
|
+
if (req.method !== "POST") {
|
|
1073
|
+
res.statusCode = 405;
|
|
1074
|
+
res.end("Method Not Allowed");
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1078
|
+
sessionIdGenerator: void 0
|
|
1079
|
+
});
|
|
1080
|
+
const server = createServer();
|
|
1081
|
+
res.on("close", () => {
|
|
1082
|
+
void transport.close();
|
|
1083
|
+
void server.close();
|
|
1084
|
+
});
|
|
1085
|
+
void server.connect(transport).then(() => transport.handleRequest(req, res)).catch((error) => {
|
|
1086
|
+
res.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
824
1089
|
vite.middlewares.use(`${base}/messages`, (req, res) => {
|
|
825
1090
|
if (req.method !== "POST") {
|
|
826
1091
|
res.statusCode = 405;
|
|
@@ -834,13 +1099,13 @@ function setupMcpTransport(base, server, vite) {
|
|
|
834
1099
|
res.end("Bad Request");
|
|
835
1100
|
return;
|
|
836
1101
|
}
|
|
837
|
-
const
|
|
838
|
-
if (!
|
|
1102
|
+
const entry = transports.get(sessionId);
|
|
1103
|
+
if (!entry) {
|
|
839
1104
|
res.statusCode = 404;
|
|
840
1105
|
res.end("Not Found");
|
|
841
1106
|
return;
|
|
842
1107
|
}
|
|
843
|
-
void transport.handlePostMessage(req, res).catch((error) => {
|
|
1108
|
+
void entry.transport.handlePostMessage(req, res).catch((error) => {
|
|
844
1109
|
res.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
845
1110
|
});
|
|
846
1111
|
});
|
|
@@ -861,6 +1126,10 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
861
1126
|
onEvaluateScriptUpdated: (event, data) => {
|
|
862
1127
|
void ctx.hooks.callHook(event, data);
|
|
863
1128
|
},
|
|
1129
|
+
takeScreenshot: () => void 0,
|
|
1130
|
+
onScreenshotTaken: (event, data) => {
|
|
1131
|
+
void ctx.hooks.callHook(event, data);
|
|
1132
|
+
},
|
|
864
1133
|
getInspectorTree: () => void 0,
|
|
865
1134
|
onInspectorTreeUpdated: (event, data) => {
|
|
866
1135
|
void ctx.hooks.callHook(event, data);
|
|
@@ -1152,13 +1421,19 @@ function createRuntimeInjectionController(options, getConfig) {
|
|
|
1152
1421
|
if (importee === VIRTUAL_RUNTIME_ID) {
|
|
1153
1422
|
return RESOLVED_VIRTUAL_RUNTIME_ID;
|
|
1154
1423
|
}
|
|
1424
|
+
if (importee === VIRTUAL_SCREENSHOT_CONFIG_ID) {
|
|
1425
|
+
return RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID;
|
|
1426
|
+
}
|
|
1155
1427
|
return void 0;
|
|
1156
1428
|
},
|
|
1157
1429
|
load(id) {
|
|
1158
|
-
if (id
|
|
1159
|
-
return
|
|
1430
|
+
if (id === RESOLVED_VIRTUAL_RUNTIME_ID) {
|
|
1431
|
+
return "import { startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';\nvoid startRuntimeClient();";
|
|
1432
|
+
}
|
|
1433
|
+
if (id === RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID) {
|
|
1434
|
+
return createScreenshotConfigModule(options);
|
|
1160
1435
|
}
|
|
1161
|
-
return
|
|
1436
|
+
return void 0;
|
|
1162
1437
|
},
|
|
1163
1438
|
transformIndexHtml(html) {
|
|
1164
1439
|
if (options.appendTo) {
|
|
@@ -1193,6 +1468,32 @@ ${code}`;
|
|
|
1193
1468
|
}
|
|
1194
1469
|
};
|
|
1195
1470
|
}
|
|
1471
|
+
function createScreenshotConfigModule(options) {
|
|
1472
|
+
const paths = collectScreenshotImportPaths(options);
|
|
1473
|
+
const imports = paths.map((item, index) => `import * as m${String(index)} from ${JSON.stringify(item)};`).join("\n");
|
|
1474
|
+
const entries = paths.map((item, index) => `${JSON.stringify(item)}: m${String(index)}`).join(",\n ");
|
|
1475
|
+
return `${imports}
|
|
1476
|
+
export const screenshotModuleRegistry = {
|
|
1477
|
+
${entries}
|
|
1478
|
+
};
|
|
1479
|
+
`;
|
|
1480
|
+
}
|
|
1481
|
+
function collectScreenshotImportPaths(options) {
|
|
1482
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1483
|
+
for (const plugin of options.screenshot.snapdom.plugins) {
|
|
1484
|
+
paths.add(getPluginPath(plugin));
|
|
1485
|
+
}
|
|
1486
|
+
if (options.screenshot.snapdom.filter) {
|
|
1487
|
+
paths.add(options.screenshot.snapdom.filter);
|
|
1488
|
+
}
|
|
1489
|
+
if (options.screenshot.snapdom.fallbackURL) {
|
|
1490
|
+
paths.add(options.screenshot.snapdom.fallbackURL);
|
|
1491
|
+
}
|
|
1492
|
+
return [...paths];
|
|
1493
|
+
}
|
|
1494
|
+
function getPluginPath(plugin) {
|
|
1495
|
+
return typeof plugin === "string" ? plugin : plugin.path;
|
|
1496
|
+
}
|
|
1196
1497
|
|
|
1197
1498
|
// src/plugin/mcpClientConfig/index.ts
|
|
1198
1499
|
import path4 from "path";
|
|
@@ -1216,7 +1517,7 @@ function replaceOrAppendOwnedBlock(current, options) {
|
|
|
1216
1517
|
const block = createCodexServerBlock(options);
|
|
1217
1518
|
const matcher = createOwnedBlockMatcher(options.serverName);
|
|
1218
1519
|
if (matcher.test(current)) {
|
|
1219
|
-
return current
|
|
1520
|
+
return ensureTrailingNewline(current);
|
|
1220
1521
|
}
|
|
1221
1522
|
const separator = current.trim() ? "\n\n" : "";
|
|
1222
1523
|
return `${trimEndNewline(current)}${separator}${block}`;
|
|
@@ -1258,6 +1559,10 @@ async function readOptionalTextFile(filePath) {
|
|
|
1258
1559
|
function trimEndNewline(value) {
|
|
1259
1560
|
return value.replace(/\n+$/u, "");
|
|
1260
1561
|
}
|
|
1562
|
+
function ensureTrailingNewline(value) {
|
|
1563
|
+
return value.endsWith("\n") ? value : `${value}
|
|
1564
|
+
`;
|
|
1565
|
+
}
|
|
1261
1566
|
function escapeRegExp(value) {
|
|
1262
1567
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1263
1568
|
}
|
|
@@ -1274,12 +1579,15 @@ import path3 from "path";
|
|
|
1274
1579
|
async function updateJsonMcpClientConfig(options) {
|
|
1275
1580
|
try {
|
|
1276
1581
|
const config = await readJsonConfig(options.configPath);
|
|
1277
|
-
if (!
|
|
1582
|
+
if (!isPlainRecord2(config)) {
|
|
1278
1583
|
warnConfigFailure(options, "config root must be a JSON object");
|
|
1279
1584
|
return;
|
|
1280
1585
|
}
|
|
1281
|
-
const mcpServers =
|
|
1282
|
-
mcpServers
|
|
1586
|
+
const mcpServers = isPlainRecord2(config.mcpServers) ? config.mcpServers : {};
|
|
1587
|
+
if (Object.hasOwn(mcpServers, options.serverName)) {
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
|
|
1283
1591
|
config.mcpServers = mcpServers;
|
|
1284
1592
|
await fs3.mkdir(path3.dirname(options.configPath), { recursive: true });
|
|
1285
1593
|
await fs3.writeFile(
|
|
@@ -1308,7 +1616,7 @@ async function readOptionalTextFile2(filePath) {
|
|
|
1308
1616
|
throw error;
|
|
1309
1617
|
}
|
|
1310
1618
|
}
|
|
1311
|
-
function
|
|
1619
|
+
function isPlainRecord2(value) {
|
|
1312
1620
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1313
1621
|
}
|
|
1314
1622
|
function warnConfigFailure(options, reason) {
|
|
@@ -1324,7 +1632,7 @@ function isNodeError2(error) {
|
|
|
1324
1632
|
}
|
|
1325
1633
|
|
|
1326
1634
|
// src/plugin/mcpClientConfig/index.ts
|
|
1327
|
-
async function updateMcpClientConfigs(root,
|
|
1635
|
+
async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options) {
|
|
1328
1636
|
const serverName = options.serverName;
|
|
1329
1637
|
const jobs = [];
|
|
1330
1638
|
if (options.cursor) {
|
|
@@ -1332,7 +1640,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
|
|
|
1332
1640
|
updateJsonMcpClientConfig({
|
|
1333
1641
|
clientName: "Cursor",
|
|
1334
1642
|
configPath: path4.join(root, ".cursor", "mcp.json"),
|
|
1335
|
-
mcpUrl,
|
|
1643
|
+
mcpUrl: sseUrl,
|
|
1336
1644
|
serverName
|
|
1337
1645
|
})
|
|
1338
1646
|
);
|
|
@@ -1341,7 +1649,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
|
|
|
1341
1649
|
jobs.push(
|
|
1342
1650
|
updateCodexMcpClientConfig({
|
|
1343
1651
|
configPath: path4.join(root, ".codex", "config.toml"),
|
|
1344
|
-
mcpUrl,
|
|
1652
|
+
mcpUrl: streamableHttpUrl,
|
|
1345
1653
|
serverName
|
|
1346
1654
|
})
|
|
1347
1655
|
);
|
|
@@ -1351,7 +1659,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
|
|
|
1351
1659
|
updateJsonMcpClientConfig({
|
|
1352
1660
|
clientName: "Claude Code",
|
|
1353
1661
|
configPath: path4.join(root, ".mcp.json"),
|
|
1354
|
-
mcpUrl,
|
|
1662
|
+
mcpUrl: sseUrl,
|
|
1355
1663
|
serverName
|
|
1356
1664
|
})
|
|
1357
1665
|
);
|
|
@@ -1361,7 +1669,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
|
|
|
1361
1669
|
updateJsonMcpClientConfig({
|
|
1362
1670
|
clientName: "Trae",
|
|
1363
1671
|
configPath: path4.join(root, ".trae", "mcp.json"),
|
|
1364
|
-
mcpUrl,
|
|
1672
|
+
mcpUrl: sseUrl,
|
|
1365
1673
|
serverName
|
|
1366
1674
|
})
|
|
1367
1675
|
);
|
|
@@ -1397,8 +1705,11 @@ function vueMcpNext(userOptions = {}) {
|
|
|
1397
1705
|
timeout: -1
|
|
1398
1706
|
}
|
|
1399
1707
|
);
|
|
1400
|
-
|
|
1401
|
-
|
|
1708
|
+
setupMcpTransport(
|
|
1709
|
+
options.mcpPath,
|
|
1710
|
+
() => createMcpServer(ctx, server),
|
|
1711
|
+
server
|
|
1712
|
+
);
|
|
1402
1713
|
server.ws.on(
|
|
1403
1714
|
"vite-plugin-vue-mcp-next:page-connected",
|
|
1404
1715
|
(payload) => {
|
|
@@ -1425,12 +1736,21 @@ function vueMcpNext(userOptions = {}) {
|
|
|
1425
1736
|
}
|
|
1426
1737
|
);
|
|
1427
1738
|
const port = String(server.config.server.port || 5173);
|
|
1428
|
-
const
|
|
1739
|
+
const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
|
|
1740
|
+
const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
|
|
1429
1741
|
const root = searchForWorkspaceRoot(server.config.root);
|
|
1430
|
-
await updateMcpClientConfigs(
|
|
1742
|
+
await updateMcpClientConfigs(
|
|
1743
|
+
root,
|
|
1744
|
+
mcpSseUrl,
|
|
1745
|
+
mcpStreamableHttpUrl,
|
|
1746
|
+
options.mcpClients
|
|
1747
|
+
);
|
|
1431
1748
|
if (options.printUrl) {
|
|
1432
1749
|
setTimeout(() => {
|
|
1433
|
-
console.log(` \u279C MCP:
|
|
1750
|
+
console.log(` \u279C MCP: SSE server is running at ${mcpSseUrl}`);
|
|
1751
|
+
console.log(
|
|
1752
|
+
` \u279C MCP: Streamable HTTP server is running at ${mcpStreamableHttpUrl}`
|
|
1753
|
+
);
|
|
1434
1754
|
}, 300);
|
|
1435
1755
|
}
|
|
1436
1756
|
server.httpServer?.once("close", () => {
|