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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot-specific type definitions
|
|
3
|
+
*/
|
|
4
|
+
import type { Page, Browser, BrowserContext, ElectronApplication } from "playwright";
|
|
5
|
+
/**
|
|
6
|
+
* Options for taking a screenshot
|
|
7
|
+
*/
|
|
8
|
+
export interface ScreenshotOptions {
|
|
9
|
+
fullPage?: boolean;
|
|
10
|
+
maxWidth?: number;
|
|
11
|
+
quality?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Result of a captured and optimized screenshot
|
|
15
|
+
*/
|
|
16
|
+
export interface ScreenshotResult {
|
|
17
|
+
data: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
originalSize: number;
|
|
22
|
+
optimizedSize: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Stored reference to a Playwright Page for screenshot access
|
|
26
|
+
*/
|
|
27
|
+
export interface PageReference {
|
|
28
|
+
type: "web" | "electron";
|
|
29
|
+
page: Page;
|
|
30
|
+
browser?: Browser;
|
|
31
|
+
browserContext?: BrowserContext;
|
|
32
|
+
electronApp?: ElectronApplication;
|
|
33
|
+
url?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Data stored from an auto-capture event
|
|
37
|
+
*/
|
|
38
|
+
export interface AutoCaptureData {
|
|
39
|
+
imageBase64: string;
|
|
40
|
+
mimeType: string;
|
|
41
|
+
url: string;
|
|
42
|
+
capturedAt: Date;
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server factory
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { SessionManager } from "./session-manager.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create and configure the Feedback MCP server
|
|
8
|
+
* Does not connect transport - that's the caller's responsibility
|
|
9
|
+
*/
|
|
10
|
+
export declare function createServer(sessionManager: SessionManager): McpServer;
|
package/build/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server factory
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { registerTools } from "./tools/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create and configure the Feedback MCP server
|
|
8
|
+
* Does not connect transport - that's the caller's responsibility
|
|
9
|
+
*/
|
|
10
|
+
export function createServer(sessionManager) {
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "feedback",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
});
|
|
15
|
+
// Register MCP tools
|
|
16
|
+
registerTools(server, sessionManager);
|
|
17
|
+
return server;
|
|
18
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle management
|
|
3
|
+
* Tracks multiple concurrent sessions with unique UUIDs
|
|
4
|
+
*/
|
|
5
|
+
import { Session, Resource } from "./types/index.js";
|
|
6
|
+
import { PageReference, AutoCaptureData } from "./screenshot/types.js";
|
|
7
|
+
import { Collector, ConsoleEntry, ErrorEntry, NetworkEntry, ProcessOutputEntry } from "./capture/types.js";
|
|
8
|
+
export declare class SessionManager {
|
|
9
|
+
private sessions;
|
|
10
|
+
private pageRefs;
|
|
11
|
+
private autoCaptures;
|
|
12
|
+
private consoleCollectors;
|
|
13
|
+
private errorCollectors;
|
|
14
|
+
private networkCollectors;
|
|
15
|
+
private processCollectors;
|
|
16
|
+
/**
|
|
17
|
+
* Create a new session with a unique UUID
|
|
18
|
+
* @returns Session ID
|
|
19
|
+
*/
|
|
20
|
+
create(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get a session by ID
|
|
23
|
+
* @returns Session or undefined if not found
|
|
24
|
+
*/
|
|
25
|
+
get(id: string): Session | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* List all active session IDs
|
|
28
|
+
* @returns Array of session IDs
|
|
29
|
+
*/
|
|
30
|
+
list(): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Add a resource to a session
|
|
33
|
+
* @throws Error if session not found
|
|
34
|
+
*/
|
|
35
|
+
addResource(sessionId: string, resource: Resource): void;
|
|
36
|
+
/**
|
|
37
|
+
* Store a typed Playwright Page reference for a session
|
|
38
|
+
* Key format: ${sessionId}:${identifier} where identifier is URL or 'electron'
|
|
39
|
+
*/
|
|
40
|
+
setPageRef(sessionId: string, identifier: string, ref: PageReference): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get a specific page reference by session and identifier
|
|
43
|
+
*/
|
|
44
|
+
getPageRef(sessionId: string, identifier: string): PageReference | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Get all page references for a session
|
|
47
|
+
*/
|
|
48
|
+
getPageRefs(sessionId: string): PageReference[];
|
|
49
|
+
/**
|
|
50
|
+
* Remove a specific page reference
|
|
51
|
+
*/
|
|
52
|
+
removePageRef(sessionId: string, identifier: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Store the latest auto-captured screenshot for a session
|
|
55
|
+
*/
|
|
56
|
+
setAutoCapture(sessionId: string, data: AutoCaptureData): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get the latest auto-captured screenshot for a session
|
|
59
|
+
*/
|
|
60
|
+
getAutoCapture(sessionId: string): AutoCaptureData | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Store a console collector for a session
|
|
63
|
+
*/
|
|
64
|
+
setConsoleCollector(sessionId: string, identifier: string, collector: Collector<ConsoleEntry>): void;
|
|
65
|
+
/**
|
|
66
|
+
* Get a specific console collector by session and identifier
|
|
67
|
+
*/
|
|
68
|
+
getConsoleCollector(sessionId: string, identifier: string): Collector<ConsoleEntry> | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Get all console collectors for a session
|
|
71
|
+
*/
|
|
72
|
+
getConsoleCollectors(sessionId: string): Collector<ConsoleEntry>[];
|
|
73
|
+
/**
|
|
74
|
+
* Store an error collector for a session
|
|
75
|
+
*/
|
|
76
|
+
setErrorCollector(sessionId: string, identifier: string, collector: Collector<ErrorEntry>): void;
|
|
77
|
+
/**
|
|
78
|
+
* Get a specific error collector by session and identifier
|
|
79
|
+
*/
|
|
80
|
+
getErrorCollector(sessionId: string, identifier: string): Collector<ErrorEntry> | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Get all error collectors for a session
|
|
83
|
+
*/
|
|
84
|
+
getErrorCollectors(sessionId: string): Collector<ErrorEntry>[];
|
|
85
|
+
/**
|
|
86
|
+
* Store a network collector for a session
|
|
87
|
+
*/
|
|
88
|
+
setNetworkCollector(sessionId: string, identifier: string, collector: Collector<NetworkEntry>): void;
|
|
89
|
+
/**
|
|
90
|
+
* Get a specific network collector by session and identifier
|
|
91
|
+
*/
|
|
92
|
+
getNetworkCollector(sessionId: string, identifier: string): Collector<NetworkEntry> | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Get all network collectors for a session
|
|
95
|
+
*/
|
|
96
|
+
getNetworkCollectors(sessionId: string): Collector<NetworkEntry>[];
|
|
97
|
+
/**
|
|
98
|
+
* Store a process output collector for a session
|
|
99
|
+
*/
|
|
100
|
+
setProcessCollector(sessionId: string, identifier: string, collector: Collector<ProcessOutputEntry>): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get a specific process collector by session and identifier
|
|
103
|
+
*/
|
|
104
|
+
getProcessCollector(sessionId: string, identifier: string): Collector<ProcessOutputEntry> | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Get all process collectors for a session
|
|
107
|
+
*/
|
|
108
|
+
getProcessCollectors(sessionId: string): Collector<ProcessOutputEntry>[];
|
|
109
|
+
/**
|
|
110
|
+
* Destroy a session and clean up all its resources
|
|
111
|
+
* Logs cleanup errors but doesn't throw
|
|
112
|
+
* No-op if session doesn't exist
|
|
113
|
+
*/
|
|
114
|
+
destroy(sessionId: string): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Destroy all sessions (for shutdown cleanup)
|
|
117
|
+
*/
|
|
118
|
+
destroyAll(): Promise<void>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle management
|
|
3
|
+
* Tracks multiple concurrent sessions with unique UUIDs
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
export class SessionManager {
|
|
7
|
+
sessions = new Map();
|
|
8
|
+
pageRefs = new Map();
|
|
9
|
+
autoCaptures = new Map();
|
|
10
|
+
consoleCollectors = new Map();
|
|
11
|
+
errorCollectors = new Map();
|
|
12
|
+
networkCollectors = new Map();
|
|
13
|
+
processCollectors = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Create a new session with a unique UUID
|
|
16
|
+
* @returns Session ID
|
|
17
|
+
*/
|
|
18
|
+
create() {
|
|
19
|
+
const id = randomUUID();
|
|
20
|
+
const session = {
|
|
21
|
+
id,
|
|
22
|
+
createdAt: new Date(),
|
|
23
|
+
resources: [],
|
|
24
|
+
};
|
|
25
|
+
this.sessions.set(id, session);
|
|
26
|
+
console.error(`Session created: ${id}`);
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a session by ID
|
|
31
|
+
* @returns Session or undefined if not found
|
|
32
|
+
*/
|
|
33
|
+
get(id) {
|
|
34
|
+
return this.sessions.get(id);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* List all active session IDs
|
|
38
|
+
* @returns Array of session IDs
|
|
39
|
+
*/
|
|
40
|
+
list() {
|
|
41
|
+
return Array.from(this.sessions.keys());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Add a resource to a session
|
|
45
|
+
* @throws Error if session not found
|
|
46
|
+
*/
|
|
47
|
+
addResource(sessionId, resource) {
|
|
48
|
+
const session = this.sessions.get(sessionId);
|
|
49
|
+
if (!session) {
|
|
50
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
51
|
+
}
|
|
52
|
+
session.resources.push(resource);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Store a typed Playwright Page reference for a session
|
|
56
|
+
* Key format: ${sessionId}:${identifier} where identifier is URL or 'electron'
|
|
57
|
+
*/
|
|
58
|
+
setPageRef(sessionId, identifier, ref) {
|
|
59
|
+
const session = this.sessions.get(sessionId);
|
|
60
|
+
if (!session) {
|
|
61
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
62
|
+
}
|
|
63
|
+
this.pageRefs.set(`${sessionId}:${identifier}`, ref);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get a specific page reference by session and identifier
|
|
67
|
+
*/
|
|
68
|
+
getPageRef(sessionId, identifier) {
|
|
69
|
+
return this.pageRefs.get(`${sessionId}:${identifier}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all page references for a session
|
|
73
|
+
*/
|
|
74
|
+
getPageRefs(sessionId) {
|
|
75
|
+
const refs = [];
|
|
76
|
+
const prefix = `${sessionId}:`;
|
|
77
|
+
for (const [key, ref] of this.pageRefs) {
|
|
78
|
+
if (key.startsWith(prefix)) {
|
|
79
|
+
refs.push(ref);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return refs;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Remove a specific page reference
|
|
86
|
+
*/
|
|
87
|
+
removePageRef(sessionId, identifier) {
|
|
88
|
+
this.pageRefs.delete(`${sessionId}:${identifier}`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Store the latest auto-captured screenshot for a session
|
|
92
|
+
*/
|
|
93
|
+
setAutoCapture(sessionId, data) {
|
|
94
|
+
this.autoCaptures.set(sessionId, data);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the latest auto-captured screenshot for a session
|
|
98
|
+
*/
|
|
99
|
+
getAutoCapture(sessionId) {
|
|
100
|
+
return this.autoCaptures.get(sessionId);
|
|
101
|
+
}
|
|
102
|
+
// --- Console Collectors ---
|
|
103
|
+
/**
|
|
104
|
+
* Store a console collector for a session
|
|
105
|
+
*/
|
|
106
|
+
setConsoleCollector(sessionId, identifier, collector) {
|
|
107
|
+
this.consoleCollectors.set(`${sessionId}:${identifier}`, collector);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get a specific console collector by session and identifier
|
|
111
|
+
*/
|
|
112
|
+
getConsoleCollector(sessionId, identifier) {
|
|
113
|
+
return this.consoleCollectors.get(`${sessionId}:${identifier}`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get all console collectors for a session
|
|
117
|
+
*/
|
|
118
|
+
getConsoleCollectors(sessionId) {
|
|
119
|
+
const collectors = [];
|
|
120
|
+
const prefix = `${sessionId}:`;
|
|
121
|
+
for (const [key, collector] of this.consoleCollectors) {
|
|
122
|
+
if (key.startsWith(prefix)) {
|
|
123
|
+
collectors.push(collector);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return collectors;
|
|
127
|
+
}
|
|
128
|
+
// --- Error Collectors ---
|
|
129
|
+
/**
|
|
130
|
+
* Store an error collector for a session
|
|
131
|
+
*/
|
|
132
|
+
setErrorCollector(sessionId, identifier, collector) {
|
|
133
|
+
this.errorCollectors.set(`${sessionId}:${identifier}`, collector);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get a specific error collector by session and identifier
|
|
137
|
+
*/
|
|
138
|
+
getErrorCollector(sessionId, identifier) {
|
|
139
|
+
return this.errorCollectors.get(`${sessionId}:${identifier}`);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get all error collectors for a session
|
|
143
|
+
*/
|
|
144
|
+
getErrorCollectors(sessionId) {
|
|
145
|
+
const collectors = [];
|
|
146
|
+
const prefix = `${sessionId}:`;
|
|
147
|
+
for (const [key, collector] of this.errorCollectors) {
|
|
148
|
+
if (key.startsWith(prefix)) {
|
|
149
|
+
collectors.push(collector);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return collectors;
|
|
153
|
+
}
|
|
154
|
+
// --- Network Collectors ---
|
|
155
|
+
/**
|
|
156
|
+
* Store a network collector for a session
|
|
157
|
+
*/
|
|
158
|
+
setNetworkCollector(sessionId, identifier, collector) {
|
|
159
|
+
this.networkCollectors.set(`${sessionId}:${identifier}`, collector);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get a specific network collector by session and identifier
|
|
163
|
+
*/
|
|
164
|
+
getNetworkCollector(sessionId, identifier) {
|
|
165
|
+
return this.networkCollectors.get(`${sessionId}:${identifier}`);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get all network collectors for a session
|
|
169
|
+
*/
|
|
170
|
+
getNetworkCollectors(sessionId) {
|
|
171
|
+
const collectors = [];
|
|
172
|
+
const prefix = `${sessionId}:`;
|
|
173
|
+
for (const [key, collector] of this.networkCollectors) {
|
|
174
|
+
if (key.startsWith(prefix)) {
|
|
175
|
+
collectors.push(collector);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return collectors;
|
|
179
|
+
}
|
|
180
|
+
// --- Process Collectors ---
|
|
181
|
+
/**
|
|
182
|
+
* Store a process output collector for a session
|
|
183
|
+
*/
|
|
184
|
+
setProcessCollector(sessionId, identifier, collector) {
|
|
185
|
+
this.processCollectors.set(`${sessionId}:${identifier}`, collector);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get a specific process collector by session and identifier
|
|
189
|
+
*/
|
|
190
|
+
getProcessCollector(sessionId, identifier) {
|
|
191
|
+
return this.processCollectors.get(`${sessionId}:${identifier}`);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get all process collectors for a session
|
|
195
|
+
*/
|
|
196
|
+
getProcessCollectors(sessionId) {
|
|
197
|
+
const collectors = [];
|
|
198
|
+
const prefix = `${sessionId}:`;
|
|
199
|
+
for (const [key, collector] of this.processCollectors) {
|
|
200
|
+
if (key.startsWith(prefix)) {
|
|
201
|
+
collectors.push(collector);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return collectors;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Destroy a session and clean up all its resources
|
|
208
|
+
* Logs cleanup errors but doesn't throw
|
|
209
|
+
* No-op if session doesn't exist
|
|
210
|
+
*/
|
|
211
|
+
async destroy(sessionId) {
|
|
212
|
+
const session = this.sessions.get(sessionId);
|
|
213
|
+
if (!session) {
|
|
214
|
+
return; // No-op if session doesn't exist
|
|
215
|
+
}
|
|
216
|
+
console.error(`Destroying session ${sessionId} (${session.resources.length} resources)`);
|
|
217
|
+
// Clean up page references for this session
|
|
218
|
+
const prefix = `${sessionId}:`;
|
|
219
|
+
for (const [key, ref] of this.pageRefs) {
|
|
220
|
+
if (key.startsWith(prefix)) {
|
|
221
|
+
try {
|
|
222
|
+
if (ref.browserContext) {
|
|
223
|
+
await ref.browserContext.close();
|
|
224
|
+
}
|
|
225
|
+
if (ref.browser) {
|
|
226
|
+
await ref.browser.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error(`Error cleaning up page ref in session ${sessionId}:`, error);
|
|
231
|
+
}
|
|
232
|
+
this.pageRefs.delete(key);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Clean up diagnostic collectors for this session
|
|
236
|
+
for (const [key, collector] of this.consoleCollectors) {
|
|
237
|
+
if (key.startsWith(prefix)) {
|
|
238
|
+
collector.detach();
|
|
239
|
+
this.consoleCollectors.delete(key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const [key, collector] of this.errorCollectors) {
|
|
243
|
+
if (key.startsWith(prefix)) {
|
|
244
|
+
collector.detach();
|
|
245
|
+
this.errorCollectors.delete(key);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const [key, collector] of this.networkCollectors) {
|
|
249
|
+
if (key.startsWith(prefix)) {
|
|
250
|
+
collector.detach();
|
|
251
|
+
this.networkCollectors.delete(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const [key, collector] of this.processCollectors) {
|
|
255
|
+
if (key.startsWith(prefix)) {
|
|
256
|
+
collector.detach();
|
|
257
|
+
this.processCollectors.delete(key);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Clean up auto-capture data
|
|
261
|
+
this.autoCaptures.delete(sessionId);
|
|
262
|
+
// Clean up each resource individually
|
|
263
|
+
for (const resource of session.resources) {
|
|
264
|
+
try {
|
|
265
|
+
await resource.cleanup();
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error(`Error cleaning up resource in session ${sessionId}:`, error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this.sessions.delete(sessionId);
|
|
272
|
+
console.error(`Session destroyed: ${sessionId}`);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Destroy all sessions (for shutdown cleanup)
|
|
276
|
+
*/
|
|
277
|
+
async destroyAll() {
|
|
278
|
+
const sessionIds = this.list();
|
|
279
|
+
console.error(`Destroying all sessions (${sessionIds.length} total)`);
|
|
280
|
+
for (const id of sessionIds) {
|
|
281
|
+
await this.destroy(id);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_port MCP tool (PROC-04)
|
|
3
|
+
* Reports whether a TCP port is available and suggests alternatives
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
/**
|
|
8
|
+
* Register the check_port tool with the MCP server
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerCheckPortTool(server: McpServer, _sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check_port MCP tool (PROC-04)
|
|
3
|
+
* Reports whether a TCP port is available and suggests alternatives
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createToolResult } from "../utils/errors.js";
|
|
7
|
+
import detect from "detect-port";
|
|
8
|
+
/**
|
|
9
|
+
* Register the check_port tool with the MCP server
|
|
10
|
+
*/
|
|
11
|
+
export function registerCheckPortTool(server, _sessionManager) {
|
|
12
|
+
server.tool("check_port", "Check if a TCP port is available for use. Use before launching a web server to avoid EADDRINUSE errors.", {
|
|
13
|
+
port: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(65535)
|
|
18
|
+
.describe("Port number to check availability for"),
|
|
19
|
+
}, async ({ port }) => {
|
|
20
|
+
try {
|
|
21
|
+
const availablePort = await detect(port);
|
|
22
|
+
if (availablePort === port) {
|
|
23
|
+
return createToolResult({
|
|
24
|
+
port,
|
|
25
|
+
available: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return createToolResult({
|
|
29
|
+
port,
|
|
30
|
+
available: false,
|
|
31
|
+
suggestedAlternative: availablePort,
|
|
32
|
+
message: `Port ${port} is in use. Port ${availablePort} is available.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
return createToolError(`Failed to check port ${port}`, message, "Ensure the port number is valid (1-65535)");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* click_element MCP tool
|
|
3
|
+
* Clicks elements on web or Electron pages using Playwright Locator API
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { SessionManager } from "../session-manager.js";
|
|
7
|
+
/**
|
|
8
|
+
* Register the click_element 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 registerClickElementTool(server: McpServer, sessionManager: SessionManager): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* click_element MCP tool
|
|
3
|
+
* Clicks elements on web or Electron pages using Playwright Locator API
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createToolError, createScreenshotResult } from "../utils/errors.js";
|
|
7
|
+
import { capturePlaywrightPage } from "../screenshot/capture.js";
|
|
8
|
+
import { optimizeScreenshot } from "../screenshot/optimize.js";
|
|
9
|
+
import { resolveSelector, getActivePage } from "../interaction/selectors.js";
|
|
10
|
+
/**
|
|
11
|
+
* Register the click_element tool with the MCP server
|
|
12
|
+
*
|
|
13
|
+
* @param server - MCP server instance
|
|
14
|
+
* @param sessionManager - Session manager for resource tracking
|
|
15
|
+
*/
|
|
16
|
+
export function registerClickElementTool(server, sessionManager) {
|
|
17
|
+
server.tool("click_element", "Click an element on a web or Electron page. Returns a screenshot after clicking. Use CSS selectors, text content, role, or test IDs to target elements.", {
|
|
18
|
+
sessionId: z
|
|
19
|
+
.string()
|
|
20
|
+
.describe("Session ID from create_session"),
|
|
21
|
+
selector: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe("Element selector. CSS: #id, .class, div > span. Text: text=Click me. Role: role=button[name='Submit']. Test ID: testid=my-btn"),
|
|
24
|
+
pageIdentifier: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("URL or 'electron' to target a specific page. Omit if session has only one page."),
|
|
28
|
+
button: z
|
|
29
|
+
.enum(["left", "right", "middle"])
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Mouse button (default: left)"),
|
|
32
|
+
clickCount: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(3)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Number of clicks (2 for double-click)"),
|
|
39
|
+
position: z
|
|
40
|
+
.object({ x: z.number(), y: z.number() })
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Click position within element"),
|
|
43
|
+
force: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Bypass actionability checks (use sparingly)"),
|
|
47
|
+
timeout: z
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.min(0)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Max wait time in ms (default: 30000)"),
|
|
53
|
+
}, async ({ sessionId, selector, pageIdentifier, button, clickCount, position, force, timeout, }) => {
|
|
54
|
+
try {
|
|
55
|
+
// Validate session exists
|
|
56
|
+
const session = sessionManager.get(sessionId);
|
|
57
|
+
if (!session) {
|
|
58
|
+
const availableSessions = sessionManager.list();
|
|
59
|
+
return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", availableSessions.length > 0
|
|
60
|
+
? `Available sessions: ${availableSessions.join(", ")}`
|
|
61
|
+
: "Create a session first with create_session.");
|
|
62
|
+
}
|
|
63
|
+
// Find the active page
|
|
64
|
+
const pageResult = getActivePage(sessionManager, sessionId, pageIdentifier);
|
|
65
|
+
if (!pageResult.success) {
|
|
66
|
+
return createToolError(pageResult.error, `Session: ${sessionId}`, pageResult.availablePages
|
|
67
|
+
? `Available pages: ${pageResult.availablePages.join(", ")}`
|
|
68
|
+
: undefined);
|
|
69
|
+
}
|
|
70
|
+
const { page } = pageResult;
|
|
71
|
+
// Resolve selector to Playwright Locator
|
|
72
|
+
const locator = resolveSelector(page, selector);
|
|
73
|
+
// Perform the click with provided options
|
|
74
|
+
await locator.click({
|
|
75
|
+
button: button ?? undefined,
|
|
76
|
+
clickCount: clickCount ?? undefined,
|
|
77
|
+
position: position ?? undefined,
|
|
78
|
+
force: force ?? undefined,
|
|
79
|
+
timeout: timeout ?? 30000,
|
|
80
|
+
});
|
|
81
|
+
// Wait briefly for any navigation triggered by the click
|
|
82
|
+
await Promise.race([
|
|
83
|
+
page.waitForLoadState("load").catch(() => { }),
|
|
84
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
85
|
+
]);
|
|
86
|
+
// Capture post-click screenshot
|
|
87
|
+
const rawBuffer = await capturePlaywrightPage(page, {
|
|
88
|
+
fullPage: false,
|
|
89
|
+
});
|
|
90
|
+
const optimized = await optimizeScreenshot(rawBuffer, {
|
|
91
|
+
maxWidth: 1280,
|
|
92
|
+
quality: 80,
|
|
93
|
+
});
|
|
94
|
+
const imageBase64 = optimized.data.toString("base64");
|
|
95
|
+
return createScreenshotResult({
|
|
96
|
+
sessionId,
|
|
97
|
+
action: "click",
|
|
98
|
+
selector,
|
|
99
|
+
button: button ?? "left",
|
|
100
|
+
success: true,
|
|
101
|
+
}, imageBase64, optimized.mimeType);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
// Strict mode violation: selector matched multiple elements
|
|
106
|
+
if (message.includes("strict mode violation")) {
|
|
107
|
+
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.");
|
|
108
|
+
}
|
|
109
|
+
// Timeout: element not found or not actionable within timeout
|
|
110
|
+
if (message.includes("Timeout") ||
|
|
111
|
+
message.includes("timeout")) {
|
|
112
|
+
return createToolError("Element not found within timeout", `Selector "${selector}" did not match any visible element within ${timeout ?? 30000}ms`, "Check the selector is correct, the element is visible, or increase the timeout. Take a screenshot first to verify the page state.");
|
|
113
|
+
}
|
|
114
|
+
// Default error
|
|
115
|
+
return createToolError("Failed to click element", `Selector: "${selector}" — ${message}`, "Take a screenshot to verify the element exists and is visible on the page.");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool 19: get_console_logs
|
|
3
|
+
* Retrieves browser console 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 registerGetConsoleLogsTool(server: McpServer, sessionManager: SessionManager): void;
|