auto-feedback 0.1.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/README.md +180 -0
- package/build/capture/console-collector.d.ts +16 -0
- package/build/capture/console-collector.js +43 -0
- package/build/capture/error-collector.d.ts +15 -0
- package/build/capture/error-collector.js +47 -0
- package/build/capture/network-collector.d.ts +16 -0
- package/build/capture/network-collector.js +76 -0
- package/build/capture/process-collector.d.ts +16 -0
- package/build/capture/process-collector.js +48 -0
- package/build/capture/types.d.ts +61 -0
- package/build/capture/types.js +5 -0
- package/build/index.d.ts +6 -0
- package/build/index.js +41 -0
- package/build/interaction/selectors.d.ts +26 -0
- package/build/interaction/selectors.js +84 -0
- package/build/interaction/types.d.ts +56 -0
- package/build/interaction/types.js +5 -0
- package/build/process/cleanup.d.ts +23 -0
- package/build/process/cleanup.js +50 -0
- package/build/process/launcher.d.ts +22 -0
- package/build/process/launcher.js +54 -0
- package/build/process/monitor.d.ts +14 -0
- package/build/process/monitor.js +67 -0
- package/build/process/types.d.ts +84 -0
- package/build/process/types.js +5 -0
- package/build/screenshot/auto-capture.d.ts +14 -0
- package/build/screenshot/auto-capture.js +38 -0
- package/build/screenshot/capture.d.ts +21 -0
- package/build/screenshot/capture.js +48 -0
- package/build/screenshot/optimize.d.ts +19 -0
- package/build/screenshot/optimize.js +28 -0
- package/build/screenshot/types.d.ts +43 -0
- package/build/screenshot/types.js +4 -0
- package/build/server.d.ts +10 -0
- package/build/server.js +18 -0
- package/build/session-manager.d.ts +119 -0
- package/build/session-manager.js +284 -0
- package/build/tools/check-port.d.ts +10 -0
- package/build/tools/check-port.js +40 -0
- package/build/tools/click-element.d.ts +13 -0
- package/build/tools/click-element.js +118 -0
- package/build/tools/get-console-logs.d.ts +7 -0
- package/build/tools/get-console-logs.js +55 -0
- package/build/tools/get-element-state.d.ts +14 -0
- package/build/tools/get-element-state.js +116 -0
- package/build/tools/get-errors.d.ts +7 -0
- package/build/tools/get-errors.js +40 -0
- package/build/tools/get-network-logs.d.ts +7 -0
- package/build/tools/get-network-logs.js +58 -0
- package/build/tools/get-process-output.d.ts +7 -0
- package/build/tools/get-process-output.js +55 -0
- package/build/tools/get-screenshot.d.ts +7 -0
- package/build/tools/get-screenshot.js +32 -0
- package/build/tools/index.d.ts +9 -0
- package/build/tools/index.js +117 -0
- package/build/tools/launch-electron.d.ts +13 -0
- package/build/tools/launch-electron.js +97 -0
- package/build/tools/launch-web-server.d.ts +13 -0
- package/build/tools/launch-web-server.js +88 -0
- package/build/tools/launch-windows-exe.d.ts +13 -0
- package/build/tools/launch-windows-exe.js +81 -0
- package/build/tools/navigate.d.ts +13 -0
- package/build/tools/navigate.js +137 -0
- package/build/tools/run-workflow.d.ts +14 -0
- package/build/tools/run-workflow.js +207 -0
- package/build/tools/screenshot-desktop.d.ts +13 -0
- package/build/tools/screenshot-desktop.js +80 -0
- package/build/tools/screenshot-electron.d.ts +13 -0
- package/build/tools/screenshot-electron.js +72 -0
- package/build/tools/screenshot-web.d.ts +13 -0
- package/build/tools/screenshot-web.js +129 -0
- package/build/tools/stop-process.d.ts +14 -0
- package/build/tools/stop-process.js +41 -0
- package/build/tools/type-text.d.ts +13 -0
- package/build/tools/type-text.js +137 -0
- package/build/tools/wait-for-element.d.ts +14 -0
- package/build/tools/wait-for-element.js +93 -0
- package/build/types/index.d.ts +31 -0
- package/build/types/index.js +4 -0
- package/build/utils/errors.d.ts +26 -0
- package/build/utils/errors.js +62 -0
- package/build/utils/shutdown.d.ts +16 -0
- package/build/utils/shutdown.js +34 -0
- package/build/workflow/assertions.d.ts +25 -0
- package/build/workflow/assertions.js +326 -0
- package/build/workflow/executor.d.ts +34 -0
- package/build/workflow/executor.js +269 -0
- package/build/workflow/types.d.ts +95 -0
- package/build/workflow/types.js +6 -0
- package/package.json +36 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 19: get_console_logs
|
|
3
|
+
* Retrieves browser console logs captured during a session.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
7
|
+
export function registerGetConsoleLogsTool(server, sessionManager) {
|
|
8
|
+
server.tool("get_console_logs", "Get browser console logs captured during the session. Use to check for warnings, errors, or debug output from web apps.", {
|
|
9
|
+
sessionId: z.string().describe("Session ID to get logs from"),
|
|
10
|
+
level: z
|
|
11
|
+
.enum(["all", "log", "error", "warning", "info", "debug"])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Filter by log level (default: all)"),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(1000)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Max entries to return, most recent first (default: 100)"),
|
|
21
|
+
}, async ({ sessionId, level, limit }) => {
|
|
22
|
+
const session = sessionManager.get(sessionId);
|
|
23
|
+
if (!session) {
|
|
24
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", "Create a session first with create_session.");
|
|
25
|
+
}
|
|
26
|
+
const collectors = sessionManager.getConsoleCollectors(sessionId);
|
|
27
|
+
if (collectors.length === 0) {
|
|
28
|
+
return createToolResult({
|
|
29
|
+
count: 0,
|
|
30
|
+
truncated: false,
|
|
31
|
+
entries: [],
|
|
32
|
+
note: "No console logs captured yet — ensure a page exists in this session",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Aggregate entries from all collectors
|
|
36
|
+
let entries = collectors.flatMap((c) => [...c.getEntries()]);
|
|
37
|
+
// Filter by level if specified
|
|
38
|
+
if (level && level !== "all") {
|
|
39
|
+
entries = entries.filter((entry) => entry.level === level);
|
|
40
|
+
}
|
|
41
|
+
// Sort by timestamp descending (newest first)
|
|
42
|
+
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
43
|
+
// Apply limit
|
|
44
|
+
const maxEntries = limit ?? 100;
|
|
45
|
+
const truncated = entries.length > maxEntries;
|
|
46
|
+
const totalCount = entries.length;
|
|
47
|
+
entries = entries.slice(0, maxEntries);
|
|
48
|
+
return createToolResult({
|
|
49
|
+
count: entries.length,
|
|
50
|
+
totalAvailable: totalCount,
|
|
51
|
+
truncated,
|
|
52
|
+
entries,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_element_state MCP tool
|
|
3
|
+
* Reads comprehensive UI element state in a single call: text, visibility,
|
|
4
|
+
* enabled/editable status, attributes, and bounding box.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { SessionManager } from "../session-manager.js";
|
|
8
|
+
/**
|
|
9
|
+
* Register the get_element_state tool with the MCP server
|
|
10
|
+
*
|
|
11
|
+
* @param server - MCP server instance
|
|
12
|
+
* @param sessionManager - Session manager for resource tracking
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerGetElementStateTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_element_state MCP tool
|
|
3
|
+
* Reads comprehensive UI element state in a single call: text, visibility,
|
|
4
|
+
* enabled/editable status, attributes, and bounding box.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
8
|
+
import { resolveSelector, getActivePage } from "../interaction/selectors.js";
|
|
9
|
+
/**
|
|
10
|
+
* Register the get_element_state tool with the MCP server
|
|
11
|
+
*
|
|
12
|
+
* @param server - MCP server instance
|
|
13
|
+
* @param sessionManager - Session manager for resource tracking
|
|
14
|
+
*/
|
|
15
|
+
export function registerGetElementStateTool(server, sessionManager) {
|
|
16
|
+
server.tool("get_element_state", "Read element state: text, visibility, enabled, attributes. Use to verify UI state without taking a screenshot.", {
|
|
17
|
+
sessionId: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Session ID from create_session"),
|
|
20
|
+
selector: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe("Element selector. CSS: #id, .class, div > span. Text: text=Click me. Role: role=button[name='Submit']. Test ID: testid=my-btn"),
|
|
23
|
+
pageIdentifier: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("URL or 'electron' to target a specific page. Omit if session has only one page."),
|
|
27
|
+
attributes: z
|
|
28
|
+
.array(z.string())
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Attribute names to read (e.g., ['href', 'class', 'aria-label']). Omit to skip attribute reading."),
|
|
31
|
+
timeout: z
|
|
32
|
+
.number()
|
|
33
|
+
.int()
|
|
34
|
+
.min(0)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Max wait time in ms (default: 30000)"),
|
|
37
|
+
}, async ({ sessionId, selector, pageIdentifier, attributes, timeout }) => {
|
|
38
|
+
try {
|
|
39
|
+
// Validate session exists
|
|
40
|
+
const session = sessionManager.get(sessionId);
|
|
41
|
+
if (!session) {
|
|
42
|
+
const availableSessions = sessionManager.list();
|
|
43
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", availableSessions.length > 0
|
|
44
|
+
? `Available sessions: ${availableSessions.join(", ")}`
|
|
45
|
+
: "Create a session first with create_session.");
|
|
46
|
+
}
|
|
47
|
+
// Find the active page
|
|
48
|
+
const pageResult = getActivePage(sessionManager, sessionId, pageIdentifier);
|
|
49
|
+
if (!pageResult.success) {
|
|
50
|
+
return createToolError(pageResult.error, `Session: ${sessionId}`, pageResult.availablePages
|
|
51
|
+
? `Available pages: ${pageResult.availablePages.join(", ")}`
|
|
52
|
+
: undefined);
|
|
53
|
+
}
|
|
54
|
+
const { page } = pageResult;
|
|
55
|
+
const effectiveTimeout = timeout ?? 30000;
|
|
56
|
+
// Resolve selector to Playwright Locator
|
|
57
|
+
const locator = resolveSelector(page, selector);
|
|
58
|
+
// Auto-wait gate: ensure element is in DOM before reading state
|
|
59
|
+
await locator.waitFor({
|
|
60
|
+
state: "attached",
|
|
61
|
+
timeout: effectiveTimeout,
|
|
62
|
+
});
|
|
63
|
+
// Read core state properties in parallel
|
|
64
|
+
const [text, innerTextVal, visible, enabled, editable] = await Promise.all([
|
|
65
|
+
locator.textContent({ timeout: effectiveTimeout }),
|
|
66
|
+
locator.innerText({ timeout: effectiveTimeout }),
|
|
67
|
+
locator.isVisible(),
|
|
68
|
+
locator.isEnabled({ timeout: effectiveTimeout }),
|
|
69
|
+
locator.isEditable({ timeout: effectiveTimeout }).catch(() => false),
|
|
70
|
+
]);
|
|
71
|
+
// Read optional properties (return null for non-applicable elements)
|
|
72
|
+
const [inputVal, checked] = await Promise.all([
|
|
73
|
+
locator.inputValue({ timeout: effectiveTimeout }).catch(() => null),
|
|
74
|
+
locator.isChecked({ timeout: effectiveTimeout }).catch(() => null),
|
|
75
|
+
]);
|
|
76
|
+
// Read bounding box (null if element not visible)
|
|
77
|
+
const box = await locator.boundingBox();
|
|
78
|
+
// Read requested attributes
|
|
79
|
+
const attrs = {};
|
|
80
|
+
if (attributes) {
|
|
81
|
+
for (const name of attributes) {
|
|
82
|
+
attrs[name] = await locator.getAttribute(name, {
|
|
83
|
+
timeout: effectiveTimeout,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Return structured state (text-only, no screenshot)
|
|
88
|
+
return createToolResult({
|
|
89
|
+
selector,
|
|
90
|
+
visible,
|
|
91
|
+
enabled,
|
|
92
|
+
editable,
|
|
93
|
+
checked,
|
|
94
|
+
textContent: text,
|
|
95
|
+
innerText: innerTextVal,
|
|
96
|
+
inputValue: inputVal,
|
|
97
|
+
attributes: attrs,
|
|
98
|
+
boundingBox: box,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
+
// Strict mode violation: selector matched multiple elements
|
|
104
|
+
if (message.includes("strict mode violation")) {
|
|
105
|
+
return createToolError("Selector matched multiple elements", `Selector "${selector}" matched more than one element (strict mode violation)`, "Use a more specific selector or add :nth-child(), :first-of-type, or similar to target a single element.");
|
|
106
|
+
}
|
|
107
|
+
// Timeout: element not found or not attached within timeout
|
|
108
|
+
if (message.includes("Timeout") ||
|
|
109
|
+
message.includes("timeout")) {
|
|
110
|
+
return createToolError("Element not found within timeout", `Selector "${selector}" did not match any element within ${timeout ?? 30000}ms`, "Check the selector is correct, the element exists in the DOM, or increase the timeout. Take a screenshot first to verify the page state.");
|
|
111
|
+
}
|
|
112
|
+
// Default error
|
|
113
|
+
return createToolError("Failed to read element state", `Selector: "${selector}" — ${message}`, "Take a screenshot to verify the element exists and is visible on the page.");
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 20: get_errors
|
|
3
|
+
* Retrieves runtime errors and page crashes captured during a session.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
export declare function registerGetErrorsTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 20: get_errors
|
|
3
|
+
* Retrieves runtime errors and page crashes captured during a session.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
7
|
+
export function registerGetErrorsTool(server, sessionManager) {
|
|
8
|
+
server.tool("get_errors", "Get runtime errors and page crashes captured during the session. Use to diagnose uncaught exceptions or page crashes in web apps.", {
|
|
9
|
+
sessionId: z.string().describe("Session ID to get errors from"),
|
|
10
|
+
type: z
|
|
11
|
+
.enum(["all", "uncaught-exception", "page-crash"])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Filter by error type (default: all)"),
|
|
14
|
+
}, async ({ sessionId, type }) => {
|
|
15
|
+
const session = sessionManager.get(sessionId);
|
|
16
|
+
if (!session) {
|
|
17
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", "Create a session first with create_session.");
|
|
18
|
+
}
|
|
19
|
+
const collectors = sessionManager.getErrorCollectors(sessionId);
|
|
20
|
+
if (collectors.length === 0) {
|
|
21
|
+
return createToolResult({
|
|
22
|
+
count: 0,
|
|
23
|
+
entries: [],
|
|
24
|
+
note: "No errors captured yet — ensure a page exists in this session",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// Aggregate entries from all collectors
|
|
28
|
+
let entries = collectors.flatMap((c) => [...c.getEntries()]);
|
|
29
|
+
// Filter by type if specified
|
|
30
|
+
if (type && type !== "all") {
|
|
31
|
+
entries = entries.filter((entry) => entry.type === type);
|
|
32
|
+
}
|
|
33
|
+
// Sort by timestamp descending (newest first)
|
|
34
|
+
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
35
|
+
return createToolResult({
|
|
36
|
+
count: entries.length,
|
|
37
|
+
entries,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 21: get_network_logs
|
|
3
|
+
* Retrieves HTTP request/response logs captured during a session.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
export declare function registerGetNetworkLogsTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 21: get_network_logs
|
|
3
|
+
* Retrieves HTTP request/response logs captured during a session.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
7
|
+
export function registerGetNetworkLogsTool(server, sessionManager) {
|
|
8
|
+
server.tool("get_network_logs", "Get HTTP request/response logs captured during the session. Use to inspect API calls, failed requests, or slow responses in web apps.", {
|
|
9
|
+
sessionId: z.string().describe("Session ID to get network logs from"),
|
|
10
|
+
filter: z
|
|
11
|
+
.enum(["all", "failed", "errors"])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Filter: 'failed' = status 0 (network errors), 'errors' = status >= 400 or 0 (default: all)"),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(500)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Max entries to return, most recent first (default: 100)"),
|
|
21
|
+
}, async ({ sessionId, filter, limit }) => {
|
|
22
|
+
const session = sessionManager.get(sessionId);
|
|
23
|
+
if (!session) {
|
|
24
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", "Create a session first with create_session.");
|
|
25
|
+
}
|
|
26
|
+
const collectors = sessionManager.getNetworkCollectors(sessionId);
|
|
27
|
+
if (collectors.length === 0) {
|
|
28
|
+
return createToolResult({
|
|
29
|
+
count: 0,
|
|
30
|
+
truncated: false,
|
|
31
|
+
entries: [],
|
|
32
|
+
note: "No network logs captured yet — ensure a page exists in this session",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Aggregate entries from all collectors
|
|
36
|
+
let entries = collectors.flatMap((c) => [...c.getEntries()]);
|
|
37
|
+
// Apply filter
|
|
38
|
+
if (filter === "failed") {
|
|
39
|
+
entries = entries.filter((entry) => entry.status === 0);
|
|
40
|
+
}
|
|
41
|
+
else if (filter === "errors") {
|
|
42
|
+
entries = entries.filter((entry) => entry.status === 0 || entry.status >= 400);
|
|
43
|
+
}
|
|
44
|
+
// Sort by timestamp descending (newest first)
|
|
45
|
+
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
46
|
+
// Apply limit
|
|
47
|
+
const maxEntries = limit ?? 100;
|
|
48
|
+
const truncated = entries.length > maxEntries;
|
|
49
|
+
const totalCount = entries.length;
|
|
50
|
+
entries = entries.slice(0, maxEntries);
|
|
51
|
+
return createToolResult({
|
|
52
|
+
count: entries.length,
|
|
53
|
+
totalAvailable: totalCount,
|
|
54
|
+
truncated,
|
|
55
|
+
entries,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 22: get_process_output
|
|
3
|
+
* Retrieves stdout/stderr output from spawned processes in a session.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
export declare function registerGetProcessOutputTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 22: get_process_output
|
|
3
|
+
* Retrieves stdout/stderr output from spawned processes in a session.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
7
|
+
export function registerGetProcessOutputTool(server, sessionManager) {
|
|
8
|
+
server.tool("get_process_output", "Get stdout/stderr output from spawned processes in the session. Use to check dev server logs, build errors, or runtime output.", {
|
|
9
|
+
sessionId: z.string().describe("Session ID to get output from"),
|
|
10
|
+
stream: z
|
|
11
|
+
.enum(["all", "stdout", "stderr"])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Filter by output stream (default: all)"),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(5000)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Max lines to return, most recent first (default: 200)"),
|
|
21
|
+
}, async ({ sessionId, stream, limit }) => {
|
|
22
|
+
const session = sessionManager.get(sessionId);
|
|
23
|
+
if (!session) {
|
|
24
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", "Create a session first with create_session.");
|
|
25
|
+
}
|
|
26
|
+
const collectors = sessionManager.getProcessCollectors(sessionId);
|
|
27
|
+
if (collectors.length === 0) {
|
|
28
|
+
return createToolResult({
|
|
29
|
+
count: 0,
|
|
30
|
+
truncated: false,
|
|
31
|
+
entries: [],
|
|
32
|
+
note: "No process output captured yet — ensure a process exists in this session",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Aggregate entries from all collectors
|
|
36
|
+
let entries = collectors.flatMap((c) => [...c.getEntries()]);
|
|
37
|
+
// Filter by stream if specified
|
|
38
|
+
if (stream && stream !== "all") {
|
|
39
|
+
entries = entries.filter((entry) => entry.stream === stream);
|
|
40
|
+
}
|
|
41
|
+
// Sort by timestamp descending (newest first)
|
|
42
|
+
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
43
|
+
// Apply limit
|
|
44
|
+
const maxEntries = limit ?? 200;
|
|
45
|
+
const truncated = entries.length > maxEntries;
|
|
46
|
+
const totalCount = entries.length;
|
|
47
|
+
entries = entries.slice(0, maxEntries);
|
|
48
|
+
return createToolResult({
|
|
49
|
+
count: entries.length,
|
|
50
|
+
totalAvailable: totalCount,
|
|
51
|
+
truncated,
|
|
52
|
+
entries,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_screenshot MCP tool
|
|
3
|
+
* Retrieves the most recent auto-captured screenshot for a session
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
export declare function registerGetScreenshotTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_screenshot MCP tool
|
|
3
|
+
* Retrieves the most recent auto-captured screenshot for a session
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createScreenshotResult } from "../utils/errors.js";
|
|
7
|
+
export function registerGetScreenshotTool(server, sessionManager) {
|
|
8
|
+
server.tool("get_screenshot", "Get the most recent auto-captured screenshot for a session. Use to check current visual state without triggering a new capture.", {
|
|
9
|
+
sessionId: z.string().describe("Session ID to get the screenshot for"),
|
|
10
|
+
}, async ({ sessionId }) => {
|
|
11
|
+
try {
|
|
12
|
+
const session = sessionManager.get(sessionId);
|
|
13
|
+
if (!session) {
|
|
14
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", "Create a session first with create_session.");
|
|
15
|
+
}
|
|
16
|
+
const autoCapture = sessionManager.getAutoCapture(sessionId);
|
|
17
|
+
if (!autoCapture) {
|
|
18
|
+
return createToolError("No auto-captured screenshot available", `Session ${sessionId} has no auto-capture yet. Auto-captures are taken after page navigations in web and Electron apps.`, "Use screenshot_web, screenshot_electron, or screenshot_desktop for an on-demand capture. Or navigate the app to trigger an auto-capture.");
|
|
19
|
+
}
|
|
20
|
+
return createScreenshotResult({
|
|
21
|
+
sessionId,
|
|
22
|
+
source: "auto-capture",
|
|
23
|
+
url: autoCapture.url,
|
|
24
|
+
capturedAt: autoCapture.capturedAt.toISOString(),
|
|
25
|
+
}, autoCapture.imageBase64, autoCapture.mimeType);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
return createToolError("Failed to retrieve screenshot", message, "Check the session ID is valid.");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool registration
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { SessionManager } from "../session-manager.js";
|
|
6
|
+
/**
|
|
7
|
+
* Register all MCP tools with the server
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerTools(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool registration
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
6
|
+
import { registerCheckPortTool } from "./check-port.js";
|
|
7
|
+
import { registerLaunchWebServerTool } from "./launch-web-server.js";
|
|
8
|
+
import { registerLaunchElectronTool } from "./launch-electron.js";
|
|
9
|
+
import { registerLaunchWindowsExeTool } from "./launch-windows-exe.js";
|
|
10
|
+
import { registerStopProcessTool } from "./stop-process.js";
|
|
11
|
+
import { registerScreenshotWebTool } from "./screenshot-web.js";
|
|
12
|
+
import { registerScreenshotElectronTool } from "./screenshot-electron.js";
|
|
13
|
+
import { registerScreenshotDesktopTool } from "./screenshot-desktop.js";
|
|
14
|
+
import { registerGetScreenshotTool } from "./get-screenshot.js";
|
|
15
|
+
import { registerClickElementTool } from "./click-element.js";
|
|
16
|
+
import { registerTypeTextTool } from "./type-text.js";
|
|
17
|
+
import { registerNavigateTool } from "./navigate.js";
|
|
18
|
+
import { registerGetElementStateTool } from "./get-element-state.js";
|
|
19
|
+
import { registerWaitForElementTool } from "./wait-for-element.js";
|
|
20
|
+
import { registerGetConsoleLogsTool } from "./get-console-logs.js";
|
|
21
|
+
import { registerGetErrorsTool } from "./get-errors.js";
|
|
22
|
+
import { registerGetNetworkLogsTool } from "./get-network-logs.js";
|
|
23
|
+
import { registerGetProcessOutputTool } from "./get-process-output.js";
|
|
24
|
+
import { registerRunWorkflowTool } from "./run-workflow.js";
|
|
25
|
+
/**
|
|
26
|
+
* Register all MCP tools with the server
|
|
27
|
+
*/
|
|
28
|
+
export function registerTools(server, sessionManager) {
|
|
29
|
+
// Tool 1: get_version
|
|
30
|
+
server.tool("get_version", "Get server version and capabilities. Use to verify the server is running and responsive.", {}, async () => {
|
|
31
|
+
return createToolResult({
|
|
32
|
+
name: "feedback",
|
|
33
|
+
version: "0.1.0",
|
|
34
|
+
status: "ready",
|
|
35
|
+
capabilities: [
|
|
36
|
+
"process_lifecycle",
|
|
37
|
+
"screenshots",
|
|
38
|
+
"interactions",
|
|
39
|
+
"error_capture",
|
|
40
|
+
"workflows",
|
|
41
|
+
"assertions",
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
// Tool 2: create_session
|
|
46
|
+
server.tool("create_session", "Create a new session for tracking app resources. Returns a unique session ID used by other tools.", {}, async () => {
|
|
47
|
+
const sessionId = sessionManager.create();
|
|
48
|
+
return createToolResult({
|
|
49
|
+
sessionId,
|
|
50
|
+
created: true,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// Tool 3: list_sessions
|
|
54
|
+
server.tool("list_sessions", "List all active session IDs. Use to find existing sessions or check server state.", {}, async () => {
|
|
55
|
+
const sessions = sessionManager.list();
|
|
56
|
+
return createToolResult({
|
|
57
|
+
sessions,
|
|
58
|
+
count: sessions.length,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
// Tool 4: end_session
|
|
62
|
+
server.tool("end_session", "End a session and clean up all its resources. Use when done testing an application.", {
|
|
63
|
+
sessionId: z.string().describe("The session ID to end"),
|
|
64
|
+
}, async ({ sessionId }) => {
|
|
65
|
+
const session = sessionManager.get(sessionId);
|
|
66
|
+
if (!session) {
|
|
67
|
+
const availableSessions = sessionManager.list();
|
|
68
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", availableSessions.length > 0
|
|
69
|
+
? `Available sessions: ${availableSessions.join(", ")}`
|
|
70
|
+
: "No active sessions. Create one with create_session first.");
|
|
71
|
+
}
|
|
72
|
+
await sessionManager.destroy(sessionId);
|
|
73
|
+
return createToolResult({
|
|
74
|
+
sessionId,
|
|
75
|
+
ended: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// Tool 5: check_port
|
|
79
|
+
registerCheckPortTool(server, sessionManager);
|
|
80
|
+
// Tool 6: launch_web_server
|
|
81
|
+
registerLaunchWebServerTool(server, sessionManager);
|
|
82
|
+
// Tool 7: launch_electron
|
|
83
|
+
registerLaunchElectronTool(server, sessionManager);
|
|
84
|
+
// Tool 8: launch_windows_exe
|
|
85
|
+
registerLaunchWindowsExeTool(server, sessionManager);
|
|
86
|
+
// Tool 9: stop_process
|
|
87
|
+
registerStopProcessTool(server, sessionManager);
|
|
88
|
+
// Tool 10: screenshot_web
|
|
89
|
+
registerScreenshotWebTool(server, sessionManager);
|
|
90
|
+
// Tool 11: screenshot_electron
|
|
91
|
+
registerScreenshotElectronTool(server, sessionManager);
|
|
92
|
+
// Tool 12: screenshot_desktop
|
|
93
|
+
registerScreenshotDesktopTool(server, sessionManager);
|
|
94
|
+
// Tool 13: get_screenshot
|
|
95
|
+
registerGetScreenshotTool(server, sessionManager);
|
|
96
|
+
// Tool 14: click_element
|
|
97
|
+
registerClickElementTool(server, sessionManager);
|
|
98
|
+
// Tool 15: type_text
|
|
99
|
+
registerTypeTextTool(server, sessionManager);
|
|
100
|
+
// Tool 16: navigate
|
|
101
|
+
registerNavigateTool(server, sessionManager);
|
|
102
|
+
// Tool 17: get_element_state
|
|
103
|
+
registerGetElementStateTool(server, sessionManager);
|
|
104
|
+
// Tool 18: wait_for_element
|
|
105
|
+
registerWaitForElementTool(server, sessionManager);
|
|
106
|
+
// Tool 19: get_console_logs
|
|
107
|
+
registerGetConsoleLogsTool(server, sessionManager);
|
|
108
|
+
// Tool 20: get_errors
|
|
109
|
+
registerGetErrorsTool(server, sessionManager);
|
|
110
|
+
// Tool 21: get_network_logs
|
|
111
|
+
registerGetNetworkLogsTool(server, sessionManager);
|
|
112
|
+
// Tool 22: get_process_output
|
|
113
|
+
registerGetProcessOutputTool(server, sessionManager);
|
|
114
|
+
// Tool 23: run_workflow
|
|
115
|
+
registerRunWorkflowTool(server, sessionManager);
|
|
116
|
+
console.error("Registered 23 MCP tools");
|
|
117
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch_electron MCP tool (PROC-02)
|
|
3
|
+
* Launches an Electron app via Playwright for automation
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
/**
|
|
8
|
+
* Register the launch_electron tool with the MCP server
|
|
9
|
+
*
|
|
10
|
+
* @param server - MCP server instance
|
|
11
|
+
* @param sessionManager - Session manager for resource tracking
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerLaunchElectronTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch_electron MCP tool (PROC-02)
|
|
3
|
+
* Launches an Electron app via Playwright for automation
|
|
4
|
+
*/
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { _electron as electron } from "playwright";
|
|
8
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
9
|
+
import { setupAutoCapture } from "../screenshot/auto-capture.js";
|
|
10
|
+
import { attachConsoleCollector } from "../capture/console-collector.js";
|
|
11
|
+
import { attachErrorCollector } from "../capture/error-collector.js";
|
|
12
|
+
import { attachNetworkCollector } from "../capture/network-collector.js";
|
|
13
|
+
/**
|
|
14
|
+
* Register the launch_electron tool with the MCP server
|
|
15
|
+
*
|
|
16
|
+
* @param server - MCP server instance
|
|
17
|
+
* @param sessionManager - Session manager for resource tracking
|
|
18
|
+
*/
|
|
19
|
+
export function registerLaunchElectronTool(server, sessionManager) {
|
|
20
|
+
server.tool("launch_electron", "Launch an Electron app via Playwright for automation. Use to start and instrument Electron desktop applications.", {
|
|
21
|
+
sessionId: z.string().describe("Session ID to track this app"),
|
|
22
|
+
entryPath: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("Path to Electron main entry file (e.g., main.js, index.js)"),
|
|
25
|
+
cwd: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Working directory (defaults to entry file directory)"),
|
|
29
|
+
timeoutMs: z
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1000)
|
|
33
|
+
.max(120000)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Launch timeout in ms (default: 30000)"),
|
|
36
|
+
}, async ({ sessionId, entryPath, cwd, timeoutMs }) => {
|
|
37
|
+
try {
|
|
38
|
+
// Validate session exists
|
|
39
|
+
const session = sessionManager.get(sessionId);
|
|
40
|
+
if (!session) {
|
|
41
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", "Create a session first with create_session.");
|
|
42
|
+
}
|
|
43
|
+
console.error(`[launch_electron] Launching Electron: ${entryPath}`);
|
|
44
|
+
// Resolve paths
|
|
45
|
+
const resolvedEntryPath = path.resolve(entryPath);
|
|
46
|
+
const resolvedCwd = cwd
|
|
47
|
+
? path.resolve(cwd)
|
|
48
|
+
: path.dirname(resolvedEntryPath);
|
|
49
|
+
// Launch Electron via Playwright
|
|
50
|
+
const electronApp = await electron.launch({
|
|
51
|
+
args: [resolvedEntryPath],
|
|
52
|
+
cwd: resolvedCwd,
|
|
53
|
+
timeout: timeoutMs ?? 30000,
|
|
54
|
+
});
|
|
55
|
+
// Wait for the first window to appear
|
|
56
|
+
const window = await electronApp.firstWindow();
|
|
57
|
+
console.error(`[launch_electron] Window detected, app ready`);
|
|
58
|
+
// Store page reference for screenshot access
|
|
59
|
+
sessionManager.setPageRef(sessionId, "electron", {
|
|
60
|
+
type: "electron",
|
|
61
|
+
page: window,
|
|
62
|
+
electronApp,
|
|
63
|
+
});
|
|
64
|
+
// Attach auto-capture on navigation events
|
|
65
|
+
const removeAutoCapture = setupAutoCapture(window, sessionId, sessionManager);
|
|
66
|
+
// Attach diagnostic collectors
|
|
67
|
+
const consoleCollector = attachConsoleCollector(window);
|
|
68
|
+
const errorCollector = attachErrorCollector(window);
|
|
69
|
+
const networkCollector = attachNetworkCollector(window);
|
|
70
|
+
sessionManager.setConsoleCollector(sessionId, "electron", consoleCollector);
|
|
71
|
+
sessionManager.setErrorCollector(sessionId, "electron", errorCollector);
|
|
72
|
+
sessionManager.setNetworkCollector(sessionId, "electron", networkCollector);
|
|
73
|
+
// Create a resource that cleans up the Electron app
|
|
74
|
+
const resource = {
|
|
75
|
+
cleanup: async () => {
|
|
76
|
+
console.error(`[launch_electron] Closing Electron app`);
|
|
77
|
+
removeAutoCapture();
|
|
78
|
+
sessionManager.removePageRef(sessionId, "electron");
|
|
79
|
+
await electronApp.close();
|
|
80
|
+
console.error(`[launch_electron] Electron app closed`);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
sessionManager.addResource(sessionId, resource);
|
|
84
|
+
return createToolResult({
|
|
85
|
+
sessionId,
|
|
86
|
+
type: "electron",
|
|
87
|
+
status: "ready",
|
|
88
|
+
entryPath: resolvedEntryPath,
|
|
89
|
+
windowTitle: await window.title(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
return createToolError("Failed to launch Electron app", message, "Check the entry path points to a valid Electron main file. Ensure Electron is installed in the target project.");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|