@teapotz/electron-mcp 0.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 +288 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +81 -0
- package/dist/protocol/responses.d.ts +4 -0
- package/dist/protocol/responses.js +22 -0
- package/dist/session/SessionController.d.ts +19 -0
- package/dist/session/SessionController.js +145 -0
- package/dist/session/types.d.ts +6 -0
- package/dist/session/types.js +1 -0
- package/dist/tools/connection.tools.d.ts +2 -0
- package/dist/tools/connection.tools.js +106 -0
- package/dist/tools/evaluate.tools.d.ts +3 -0
- package/dist/tools/evaluate.tools.js +73 -0
- package/dist/tools/inspection.tools.d.ts +2 -0
- package/dist/tools/inspection.tools.js +120 -0
- package/dist/tools/interaction.tools.d.ts +2 -0
- package/dist/tools/interaction.tools.js +233 -0
- package/dist/tools/mouse.tools.d.ts +2 -0
- package/dist/tools/mouse.tools.js +105 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +40 -0
- package/dist/tools/scroll.tools.d.ts +2 -0
- package/dist/tools/scroll.tools.js +84 -0
- package/dist/validation/schemas.d.ts +86 -0
- package/dist/validation/schemas.js +84 -0
- package/package.json +49 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { toolError } from "../protocol/responses.js";
|
|
2
|
+
import { NotConnectedError, NotLaunchedError, classifyPlaywrightError, } from "../errors.js";
|
|
3
|
+
import { connectionTools } from "./connection.tools.js";
|
|
4
|
+
import { interactionTools } from "./interaction.tools.js";
|
|
5
|
+
import { inspectionTools } from "./inspection.tools.js";
|
|
6
|
+
import { scrollTools } from "./scroll.tools.js";
|
|
7
|
+
import { mouseTools } from "./mouse.tools.js";
|
|
8
|
+
import { evaluateTools } from "./evaluate.tools.js";
|
|
9
|
+
export function buildRegistry() {
|
|
10
|
+
const registry = new Map();
|
|
11
|
+
const allTools = [
|
|
12
|
+
...connectionTools,
|
|
13
|
+
...interactionTools,
|
|
14
|
+
...inspectionTools,
|
|
15
|
+
...scrollTools,
|
|
16
|
+
...mouseTools,
|
|
17
|
+
...evaluateTools,
|
|
18
|
+
];
|
|
19
|
+
for (const tool of allTools) {
|
|
20
|
+
registry.set(tool.definition.name, tool);
|
|
21
|
+
}
|
|
22
|
+
return registry;
|
|
23
|
+
}
|
|
24
|
+
export async function dispatch(registry, session, name, rawArgs) {
|
|
25
|
+
const spec = registry.get(name);
|
|
26
|
+
if (!spec) {
|
|
27
|
+
return toolError(`Unknown tool: ${name}`);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = spec.parse(rawArgs);
|
|
31
|
+
return await spec.handler(session, parsed);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof NotConnectedError || error instanceof NotLaunchedError) {
|
|
35
|
+
return toolError(`Error: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
const classified = classifyPlaywrightError(error);
|
|
38
|
+
return toolError(`Error: ${classified.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { toolOk } from "../protocol/responses.js";
|
|
2
|
+
import { ScrollArgs, ScrollToArgs, SelectorArgs } from "../validation/schemas.js";
|
|
3
|
+
export const scrollTools = [
|
|
4
|
+
{
|
|
5
|
+
definition: {
|
|
6
|
+
name: "scroll",
|
|
7
|
+
description: "Scroll the page using mouse wheel",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
deltaX: {
|
|
12
|
+
type: "number",
|
|
13
|
+
description: "Horizontal scroll amount (positive = right, negative = left)",
|
|
14
|
+
},
|
|
15
|
+
deltaY: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Vertical scroll amount (positive = down, negative = up)",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
parse: (raw) => ScrollArgs.parse(raw ?? {}),
|
|
23
|
+
handler: async (session, args) => {
|
|
24
|
+
const p = session.requirePage();
|
|
25
|
+
const { deltaX, deltaY } = args;
|
|
26
|
+
await p.mouse.wheel(deltaX, deltaY);
|
|
27
|
+
return toolOk(`Scrolled by (${deltaX}, ${deltaY})`);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
definition: {
|
|
32
|
+
name: "scrollTo",
|
|
33
|
+
description: "Scroll to absolute position on the page",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
x: {
|
|
38
|
+
type: "number",
|
|
39
|
+
description: "Horizontal position in pixels (default: 0)",
|
|
40
|
+
},
|
|
41
|
+
y: {
|
|
42
|
+
type: "number",
|
|
43
|
+
description: "Vertical position in pixels (default: 0)",
|
|
44
|
+
},
|
|
45
|
+
behavior: {
|
|
46
|
+
type: "string",
|
|
47
|
+
enum: ["auto", "smooth"],
|
|
48
|
+
description: "Scroll behavior (default: auto)",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
parse: (raw) => ScrollToArgs.parse(raw ?? {}),
|
|
54
|
+
handler: async (session, args) => {
|
|
55
|
+
const p = session.requirePage();
|
|
56
|
+
const { x, y, behavior } = args;
|
|
57
|
+
await p.evaluate(({ x, y, behavior }) => window.scrollTo({ left: x, top: y, behavior }), { x, y, behavior });
|
|
58
|
+
return toolOk(`Scrolled to position (${x}, ${y})`);
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
definition: {
|
|
63
|
+
name: "scrollIntoView",
|
|
64
|
+
description: "Scroll element into view",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
selector: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "CSS selector for the element to scroll into view",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ["selector"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
parse: (raw) => SelectorArgs.parse(raw),
|
|
77
|
+
handler: async (session, args) => {
|
|
78
|
+
const p = session.requirePage();
|
|
79
|
+
const { selector } = args;
|
|
80
|
+
await p.locator(selector).scrollIntoViewIfNeeded();
|
|
81
|
+
return toolOk(`Scrolled ${selector} into view`);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ConnectArgs: z.ZodObject<{
|
|
3
|
+
port: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
4
|
+
}, z.core.$strip>;
|
|
5
|
+
export declare const LaunchArgs: z.ZodObject<{
|
|
6
|
+
appPath: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
7
|
+
env: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
8
|
+
headless: z.ZodDefault<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const EmptyArgs: z.ZodObject<{}, z.core.$strip>;
|
|
11
|
+
export declare const ScreenshotArgs: z.ZodObject<{
|
|
12
|
+
fullPage: z.ZodDefault<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const SelectorArgs: z.ZodObject<{
|
|
15
|
+
selector: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export declare const FillArgs: z.ZodObject<{
|
|
18
|
+
selector: z.ZodString;
|
|
19
|
+
text: z.ZodString;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export declare const TypeArgs: z.ZodObject<{
|
|
22
|
+
selector: z.ZodString;
|
|
23
|
+
text: z.ZodString;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export declare const PressArgs: z.ZodObject<{
|
|
26
|
+
key: z.ZodString;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export declare const WaitArgs: z.ZodObject<{
|
|
29
|
+
selector: z.ZodString;
|
|
30
|
+
state: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
31
|
+
visible: "visible";
|
|
32
|
+
hidden: "hidden";
|
|
33
|
+
attached: "attached";
|
|
34
|
+
detached: "detached";
|
|
35
|
+
}>>>;
|
|
36
|
+
waitTimeoutMs: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export declare const EvaluateArgs: z.ZodObject<{
|
|
39
|
+
script: z.ZodString;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const DragArgs: z.ZodObject<{
|
|
42
|
+
source: z.ZodString;
|
|
43
|
+
target: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export declare const SelectOptionArgs: z.ZodObject<{
|
|
46
|
+
selector: z.ZodString;
|
|
47
|
+
value: z.ZodString;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export declare const GetAttributeArgs: z.ZodObject<{
|
|
50
|
+
selector: z.ZodString;
|
|
51
|
+
attribute: z.ZodString;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export declare const ScrollArgs: z.ZodObject<{
|
|
54
|
+
deltaX: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
55
|
+
deltaY: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export declare const ScrollToArgs: z.ZodObject<{
|
|
58
|
+
x: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
59
|
+
y: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
60
|
+
behavior: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
auto: "auto";
|
|
62
|
+
smooth: "smooth";
|
|
63
|
+
}>>>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
export declare const MouseMoveArgs: z.ZodObject<{
|
|
66
|
+
x: z.ZodCoercedNumber<unknown>;
|
|
67
|
+
y: z.ZodCoercedNumber<unknown>;
|
|
68
|
+
steps: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export declare const MouseButtonArgs: z.ZodObject<{
|
|
71
|
+
button: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
72
|
+
left: "left";
|
|
73
|
+
right: "right";
|
|
74
|
+
middle: "middle";
|
|
75
|
+
}>>>;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
export declare const MouseClickArgs: z.ZodObject<{
|
|
78
|
+
x: z.ZodCoercedNumber<unknown>;
|
|
79
|
+
y: z.ZodCoercedNumber<unknown>;
|
|
80
|
+
button: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
81
|
+
left: "left";
|
|
82
|
+
right: "right";
|
|
83
|
+
middle: "middle";
|
|
84
|
+
}>>>;
|
|
85
|
+
clickCount: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
86
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Use z.coerce for backward compat (clients may send "9222" as string)
|
|
3
|
+
/** Coerce string booleans correctly ("false" → false, not true). */
|
|
4
|
+
const coerceBool = z.preprocess((val) => {
|
|
5
|
+
if (typeof val === "string") {
|
|
6
|
+
if (val === "true" || val === "1")
|
|
7
|
+
return true;
|
|
8
|
+
if (val === "false" || val === "0" || val === "")
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return val;
|
|
12
|
+
}, z.boolean());
|
|
13
|
+
export const ConnectArgs = z.object({
|
|
14
|
+
port: z.coerce.number().int().positive().optional().default(9222),
|
|
15
|
+
});
|
|
16
|
+
export const LaunchArgs = z.object({
|
|
17
|
+
appPath: z.string().optional().default("./out/main/index.js"),
|
|
18
|
+
env: z.record(z.string(), z.string()).optional().default({}),
|
|
19
|
+
headless: coerceBool.optional().default(false),
|
|
20
|
+
});
|
|
21
|
+
export const EmptyArgs = z.object({});
|
|
22
|
+
export const ScreenshotArgs = z.object({
|
|
23
|
+
fullPage: coerceBool.optional().default(false),
|
|
24
|
+
});
|
|
25
|
+
export const SelectorArgs = z.object({
|
|
26
|
+
selector: z.string().min(1),
|
|
27
|
+
});
|
|
28
|
+
export const FillArgs = z.object({
|
|
29
|
+
selector: z.string().min(1),
|
|
30
|
+
text: z.string(),
|
|
31
|
+
});
|
|
32
|
+
export const TypeArgs = z.object({
|
|
33
|
+
selector: z.string().min(1),
|
|
34
|
+
text: z.string(),
|
|
35
|
+
});
|
|
36
|
+
export const PressArgs = z.object({
|
|
37
|
+
key: z.string().min(1),
|
|
38
|
+
});
|
|
39
|
+
export const WaitArgs = z.object({
|
|
40
|
+
selector: z.string().min(1),
|
|
41
|
+
state: z
|
|
42
|
+
.enum(["visible", "hidden", "attached", "detached"])
|
|
43
|
+
.optional()
|
|
44
|
+
.default("visible"),
|
|
45
|
+
waitTimeoutMs: z.coerce.number().positive().optional().default(5000),
|
|
46
|
+
});
|
|
47
|
+
export const EvaluateArgs = z.object({
|
|
48
|
+
script: z.string().min(1),
|
|
49
|
+
});
|
|
50
|
+
export const DragArgs = z.object({
|
|
51
|
+
source: z.string().min(1),
|
|
52
|
+
target: z.string().min(1),
|
|
53
|
+
});
|
|
54
|
+
export const SelectOptionArgs = z.object({
|
|
55
|
+
selector: z.string().min(1),
|
|
56
|
+
value: z.string(),
|
|
57
|
+
});
|
|
58
|
+
export const GetAttributeArgs = z.object({
|
|
59
|
+
selector: z.string().min(1),
|
|
60
|
+
attribute: z.string().min(1),
|
|
61
|
+
});
|
|
62
|
+
export const ScrollArgs = z.object({
|
|
63
|
+
deltaX: z.coerce.number().optional().default(0),
|
|
64
|
+
deltaY: z.coerce.number().optional().default(0),
|
|
65
|
+
});
|
|
66
|
+
export const ScrollToArgs = z.object({
|
|
67
|
+
x: z.coerce.number().optional().default(0),
|
|
68
|
+
y: z.coerce.number().optional().default(0),
|
|
69
|
+
behavior: z.enum(["auto", "smooth"]).optional().default("auto"),
|
|
70
|
+
});
|
|
71
|
+
export const MouseMoveArgs = z.object({
|
|
72
|
+
x: z.coerce.number(),
|
|
73
|
+
y: z.coerce.number(),
|
|
74
|
+
steps: z.coerce.number().int().positive().optional().default(1),
|
|
75
|
+
});
|
|
76
|
+
export const MouseButtonArgs = z.object({
|
|
77
|
+
button: z.enum(["left", "right", "middle"]).optional().default("left"),
|
|
78
|
+
});
|
|
79
|
+
export const MouseClickArgs = z.object({
|
|
80
|
+
x: z.coerce.number(),
|
|
81
|
+
y: z.coerce.number(),
|
|
82
|
+
button: z.enum(["left", "right", "middle"]).optional().default("left"),
|
|
83
|
+
clickCount: z.coerce.number().int().positive().optional().default(1),
|
|
84
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teapotz/electron-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Electron UI automation via Playwright",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"electron-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"electron",
|
|
15
|
+
"testing",
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"playwright",
|
|
19
|
+
"automation",
|
|
20
|
+
"e2e",
|
|
21
|
+
"end-to-end"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"lint": "biome lint src/ tests/",
|
|
27
|
+
"format": "biome format --write src/ tests/",
|
|
28
|
+
"format:check": "biome format src/ tests/",
|
|
29
|
+
"test": "bun test tests/unit/ tests/characterization.test.ts",
|
|
30
|
+
"test:unit": "bun test tests/unit/",
|
|
31
|
+
"test:characterization": "bun test tests/characterization.test.ts",
|
|
32
|
+
"test:integration": "bun test tests/integration/",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
37
|
+
"playwright": "^1.58.2",
|
|
38
|
+
"zod": "^4.3.6"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "^1.9.4",
|
|
42
|
+
"@types/bun": "^1.2.0",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
}
|
|
49
|
+
}
|