electron-dev-bridge 0.1.1 → 0.2.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/dist/cdp-tools/devtools.d.ts +29 -0
- package/dist/cdp-tools/devtools.js +249 -0
- package/dist/cdp-tools/index.js +3 -0
- package/dist/cdp-tools/lifecycle.js +14 -0
- package/dist/cdp-tools/types.d.ts +2 -0
- package/dist/cli/index.js +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1 -0
- package/dist/server/cdp-bridge.js +9 -5
- package/dist/server/mcp-server.js +33 -3
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CdpTool, ToolContext } from './types.js';
|
|
2
|
+
export interface ConsoleEntry {
|
|
3
|
+
level: string;
|
|
4
|
+
message: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
export interface NetworkEntry {
|
|
8
|
+
requestId: string;
|
|
9
|
+
method: string;
|
|
10
|
+
url: string;
|
|
11
|
+
status?: number;
|
|
12
|
+
statusText?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
startTime: number;
|
|
15
|
+
endTime?: number;
|
|
16
|
+
duration?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class DevtoolsStore {
|
|
19
|
+
console: ConsoleEntry[];
|
|
20
|
+
network: Map<string, NetworkEntry>;
|
|
21
|
+
private attached;
|
|
22
|
+
attach(client: any): void;
|
|
23
|
+
detach(): void;
|
|
24
|
+
clearConsole(): void;
|
|
25
|
+
clearNetwork(): void;
|
|
26
|
+
clearAll(): void;
|
|
27
|
+
getNetworkEntries(): NetworkEntry[];
|
|
28
|
+
}
|
|
29
|
+
export declare function createDevtoolsTools(ctx: ToolContext): CdpTool[];
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { toolResult } from './helpers.js';
|
|
2
|
+
const MAX_CONSOLE_ENTRIES = 1000;
|
|
3
|
+
const MAX_NETWORK_ENTRIES = 500;
|
|
4
|
+
const MAX_BODY_LENGTH = 1024;
|
|
5
|
+
export class DevtoolsStore {
|
|
6
|
+
console = [];
|
|
7
|
+
network = new Map();
|
|
8
|
+
attached = false;
|
|
9
|
+
attach(client) {
|
|
10
|
+
if (this.attached)
|
|
11
|
+
return;
|
|
12
|
+
this.attached = true;
|
|
13
|
+
client.Runtime.consoleAPICalled(({ type, args, timestamp }) => {
|
|
14
|
+
const message = (args || [])
|
|
15
|
+
.map((a) => a.value ?? a.description ?? String(a.type))
|
|
16
|
+
.join(' ');
|
|
17
|
+
this.console.push({ level: type, message, timestamp });
|
|
18
|
+
if (this.console.length > MAX_CONSOLE_ENTRIES) {
|
|
19
|
+
this.console.splice(0, this.console.length - MAX_CONSOLE_ENTRIES);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
client.Network.requestWillBeSent(({ requestId, request, timestamp }) => {
|
|
23
|
+
if (this.network.size >= MAX_NETWORK_ENTRIES) {
|
|
24
|
+
const oldest = this.network.keys().next().value;
|
|
25
|
+
this.network.delete(oldest);
|
|
26
|
+
}
|
|
27
|
+
this.network.set(requestId, {
|
|
28
|
+
requestId,
|
|
29
|
+
method: request.method,
|
|
30
|
+
url: request.url,
|
|
31
|
+
startTime: timestamp,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
client.Network.responseReceived(({ requestId, response }) => {
|
|
35
|
+
const entry = this.network.get(requestId);
|
|
36
|
+
if (entry) {
|
|
37
|
+
entry.status = response.status;
|
|
38
|
+
entry.statusText = response.statusText;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
client.Network.loadingFinished(({ requestId, timestamp }) => {
|
|
42
|
+
const entry = this.network.get(requestId);
|
|
43
|
+
if (entry) {
|
|
44
|
+
entry.endTime = timestamp;
|
|
45
|
+
entry.duration = Math.round((timestamp - entry.startTime) * 1000);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
client.Network.loadingFailed(({ requestId, errorText }) => {
|
|
49
|
+
const entry = this.network.get(requestId);
|
|
50
|
+
if (entry) {
|
|
51
|
+
entry.error = errorText;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
detach() {
|
|
56
|
+
this.attached = false;
|
|
57
|
+
}
|
|
58
|
+
clearConsole() {
|
|
59
|
+
this.console = [];
|
|
60
|
+
}
|
|
61
|
+
clearNetwork() {
|
|
62
|
+
this.network.clear();
|
|
63
|
+
}
|
|
64
|
+
clearAll() {
|
|
65
|
+
this.clearConsole();
|
|
66
|
+
this.clearNetwork();
|
|
67
|
+
}
|
|
68
|
+
getNetworkEntries() {
|
|
69
|
+
return Array.from(this.network.values());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function createDevtoolsTools(ctx) {
|
|
73
|
+
const { bridge, state } = ctx;
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
definition: {
|
|
77
|
+
name: 'electron_get_console_logs',
|
|
78
|
+
description: 'Get captured console messages from the Electron app. Captures console.log/info/warn/error/debug calls.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
level: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Filter by level: log, info, warn, error, debug. Comma-separated for multiple.',
|
|
85
|
+
},
|
|
86
|
+
search: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Filter by message content (case-insensitive substring match).',
|
|
89
|
+
},
|
|
90
|
+
limit: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
description: 'Max results to return. Default: 100.',
|
|
93
|
+
},
|
|
94
|
+
since: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'ISO timestamp — only return logs after this time.',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
handler: async ({ level, search, limit = 100, since, } = {}) => {
|
|
102
|
+
bridge.ensureConnected();
|
|
103
|
+
const store = state.devtoolsStore;
|
|
104
|
+
if (!store)
|
|
105
|
+
return toolResult({ logs: [], total: 0 });
|
|
106
|
+
let entries = store.console;
|
|
107
|
+
if (level) {
|
|
108
|
+
const levels = new Set(level.split(',').map(l => l.trim().toLowerCase()));
|
|
109
|
+
entries = entries.filter(e => levels.has(e.level));
|
|
110
|
+
}
|
|
111
|
+
if (search) {
|
|
112
|
+
const lower = search.toLowerCase();
|
|
113
|
+
entries = entries.filter(e => e.message.toLowerCase().includes(lower));
|
|
114
|
+
}
|
|
115
|
+
if (since) {
|
|
116
|
+
const sinceTs = new Date(since).getTime() / 1000;
|
|
117
|
+
entries = entries.filter(e => e.timestamp >= sinceTs);
|
|
118
|
+
}
|
|
119
|
+
const total = entries.length;
|
|
120
|
+
const logs = entries.slice(-limit).map(e => ({
|
|
121
|
+
level: e.level,
|
|
122
|
+
message: e.message.length > MAX_BODY_LENGTH
|
|
123
|
+
? e.message.slice(0, MAX_BODY_LENGTH) + '...'
|
|
124
|
+
: e.message,
|
|
125
|
+
timestamp: new Date(e.timestamp * 1000).toISOString(),
|
|
126
|
+
}));
|
|
127
|
+
return toolResult({ logs, total, returned: logs.length });
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
definition: {
|
|
132
|
+
name: 'electron_get_network_requests',
|
|
133
|
+
description: 'Get captured network requests from the Electron app. Captures all HTTP/HTTPS requests with status, timing, and errors.',
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
urlPattern: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: 'Regex pattern to filter by URL.',
|
|
140
|
+
},
|
|
141
|
+
method: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: 'HTTP method filter (e.g. GET, POST).',
|
|
144
|
+
},
|
|
145
|
+
errorsOnly: {
|
|
146
|
+
type: 'boolean',
|
|
147
|
+
description: 'Only return failed requests (4xx/5xx or network errors).',
|
|
148
|
+
},
|
|
149
|
+
limit: {
|
|
150
|
+
type: 'number',
|
|
151
|
+
description: 'Max results to return. Default: 50.',
|
|
152
|
+
},
|
|
153
|
+
since: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
description: 'ISO timestamp — only return requests after this time.',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
handler: async ({ urlPattern, method, errorsOnly, limit = 50, since, } = {}) => {
|
|
161
|
+
bridge.ensureConnected();
|
|
162
|
+
const store = state.devtoolsStore;
|
|
163
|
+
if (!store)
|
|
164
|
+
return toolResult({ requests: [], total: 0 });
|
|
165
|
+
let entries = store.getNetworkEntries();
|
|
166
|
+
if (urlPattern) {
|
|
167
|
+
const re = new RegExp(urlPattern, 'i');
|
|
168
|
+
entries = entries.filter(e => re.test(e.url));
|
|
169
|
+
}
|
|
170
|
+
if (method) {
|
|
171
|
+
const upper = method.toUpperCase();
|
|
172
|
+
entries = entries.filter(e => e.method === upper);
|
|
173
|
+
}
|
|
174
|
+
if (errorsOnly) {
|
|
175
|
+
entries = entries.filter(e => e.error || (e.status !== undefined && e.status >= 400));
|
|
176
|
+
}
|
|
177
|
+
if (since) {
|
|
178
|
+
const sinceTs = new Date(since).getTime() / 1000;
|
|
179
|
+
entries = entries.filter(e => e.startTime >= sinceTs);
|
|
180
|
+
}
|
|
181
|
+
const total = entries.length;
|
|
182
|
+
const requests = entries.slice(-limit).map(e => ({
|
|
183
|
+
method: e.method,
|
|
184
|
+
url: e.url.length > MAX_BODY_LENGTH
|
|
185
|
+
? e.url.slice(0, MAX_BODY_LENGTH) + '...'
|
|
186
|
+
: e.url,
|
|
187
|
+
status: e.status,
|
|
188
|
+
statusText: e.statusText,
|
|
189
|
+
error: e.error,
|
|
190
|
+
duration: e.duration != null ? `${e.duration}ms` : undefined,
|
|
191
|
+
timestamp: new Date(e.startTime * 1000).toISOString(),
|
|
192
|
+
}));
|
|
193
|
+
return toolResult({ requests, total, returned: requests.length });
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
definition: {
|
|
198
|
+
name: 'electron_clear_devtools_data',
|
|
199
|
+
description: 'Clear captured console logs and/or network request buffers.',
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
type: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
enum: ['all', 'console', 'network'],
|
|
206
|
+
description: 'What to clear. Default: all.',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
handler: async ({ type = 'all' } = {}) => {
|
|
212
|
+
const store = state.devtoolsStore;
|
|
213
|
+
if (!store)
|
|
214
|
+
return toolResult({ cleared: type });
|
|
215
|
+
if (type === 'console')
|
|
216
|
+
store.clearConsole();
|
|
217
|
+
else if (type === 'network')
|
|
218
|
+
store.clearNetwork();
|
|
219
|
+
else
|
|
220
|
+
store.clearAll();
|
|
221
|
+
return toolResult({ cleared: type });
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
definition: {
|
|
226
|
+
name: 'electron_get_devtools_stats',
|
|
227
|
+
description: 'Get counts of captured console logs and network requests.',
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
properties: {},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
handler: async () => {
|
|
234
|
+
const store = state.devtoolsStore;
|
|
235
|
+
if (!store)
|
|
236
|
+
return toolResult({ console: 0, network: 0, capturing: false });
|
|
237
|
+
return toolResult({
|
|
238
|
+
console: store.console.length,
|
|
239
|
+
network: store.network.size,
|
|
240
|
+
capturing: true,
|
|
241
|
+
limits: {
|
|
242
|
+
maxConsole: MAX_CONSOLE_ENTRIES,
|
|
243
|
+
maxNetwork: MAX_NETWORK_ENTRIES,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
}
|
package/dist/cdp-tools/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createDevtoolsTools } from './devtools.js';
|
|
1
2
|
import { createDomQueryTools } from './dom-query.js';
|
|
2
3
|
import { createInteractionTools } from './interaction.js';
|
|
3
4
|
import { createLifecycleTools } from './lifecycle.js';
|
|
@@ -13,6 +14,7 @@ export function getCdpTools(bridge, appConfig, screenshotConfig) {
|
|
|
13
14
|
state: {
|
|
14
15
|
screenshotCounter: 0,
|
|
15
16
|
electronProcess: null,
|
|
17
|
+
devtoolsStore: null,
|
|
16
18
|
},
|
|
17
19
|
};
|
|
18
20
|
return [
|
|
@@ -22,5 +24,6 @@ export function getCdpTools(bridge, appConfig, screenshotConfig) {
|
|
|
22
24
|
...createStateTools(ctx),
|
|
23
25
|
...createNavigationTools(ctx),
|
|
24
26
|
...createVisualTools(ctx),
|
|
27
|
+
...createDevtoolsTools(ctx),
|
|
25
28
|
];
|
|
26
29
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
+
import { DevtoolsStore } from './devtools.js';
|
|
3
4
|
import { toolResult } from './helpers.js';
|
|
5
|
+
function attachDevtoolsStore(bridge, state) {
|
|
6
|
+
if (state.devtoolsStore) {
|
|
7
|
+
state.devtoolsStore.detach();
|
|
8
|
+
}
|
|
9
|
+
const store = new DevtoolsStore();
|
|
10
|
+
store.attach(bridge.getRawClient());
|
|
11
|
+
state.devtoolsStore = store;
|
|
12
|
+
}
|
|
4
13
|
export function createLifecycleTools(ctx) {
|
|
5
14
|
const { bridge, appConfig, state } = ctx;
|
|
6
15
|
return [
|
|
@@ -45,6 +54,7 @@ export function createLifecycleTools(ctx) {
|
|
|
45
54
|
'Check that the app path is correct and Electron is installed.');
|
|
46
55
|
}
|
|
47
56
|
await bridge.connect();
|
|
57
|
+
attachDevtoolsStore(bridge, state);
|
|
48
58
|
return toolResult({
|
|
49
59
|
pid: child.pid,
|
|
50
60
|
debugPort,
|
|
@@ -69,8 +79,12 @@ export function createLifecycleTools(ctx) {
|
|
|
69
79
|
},
|
|
70
80
|
handler: async ({ port } = {}) => {
|
|
71
81
|
const targetPort = port || appConfig.debugPort || 9229;
|
|
82
|
+
if (bridge.connected) {
|
|
83
|
+
return toolResult({ connected: true, port: targetPort, message: 'Already connected' });
|
|
84
|
+
}
|
|
72
85
|
bridge.setPort(targetPort);
|
|
73
86
|
await bridge.connect();
|
|
87
|
+
attachDevtoolsStore(bridge, state);
|
|
74
88
|
return toolResult({ connected: true, port: targetPort });
|
|
75
89
|
},
|
|
76
90
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChildProcess } from 'node:child_process';
|
|
2
2
|
import type { AppConfig } from '../index.js';
|
|
3
3
|
import type { CdpBridge } from '../server/cdp-bridge.js';
|
|
4
|
+
import type { DevtoolsStore } from './devtools.js';
|
|
4
5
|
export interface CdpToolDefinition {
|
|
5
6
|
name: string;
|
|
6
7
|
description: string;
|
|
@@ -18,5 +19,6 @@ export interface ToolContext {
|
|
|
18
19
|
state: {
|
|
19
20
|
screenshotCounter: number;
|
|
20
21
|
electronProcess: ChildProcess | null;
|
|
22
|
+
devtoolsStore: DevtoolsStore | null;
|
|
21
23
|
};
|
|
22
24
|
}
|
package/dist/cli/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -20,14 +20,28 @@ export interface ScreenshotConfig {
|
|
|
20
20
|
dir?: string;
|
|
21
21
|
format?: 'png' | 'jpeg';
|
|
22
22
|
}
|
|
23
|
+
export interface CustomTool {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
inputSchema: object;
|
|
27
|
+
handler: (args: Record<string, unknown>) => Promise<{
|
|
28
|
+
content: Array<{
|
|
29
|
+
type: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}>;
|
|
32
|
+
isError?: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
23
35
|
export interface ElectronMcpConfig {
|
|
24
36
|
app: AppConfig;
|
|
25
37
|
tools: Record<string, ToolConfig>;
|
|
26
38
|
resources?: Record<string, ResourceConfig>;
|
|
27
39
|
cdpTools?: boolean | string[];
|
|
28
40
|
screenshots?: ScreenshotConfig;
|
|
41
|
+
customTools?: CustomTool[];
|
|
29
42
|
}
|
|
30
43
|
export declare function defineConfig(config: ElectronMcpConfig): ElectronMcpConfig;
|
|
31
44
|
export { CdpBridge } from './server/cdp-bridge.js';
|
|
32
45
|
export { getCdpTools } from './cdp-tools/index.js';
|
|
46
|
+
export { startServer } from './server/mcp-server.js';
|
|
33
47
|
export type { CdpTool, CdpToolDefinition } from './cdp-tools/types.js';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ export class CdpBridge {
|
|
|
12
12
|
this.port = port;
|
|
13
13
|
}
|
|
14
14
|
async connect(maxRetries = 10) {
|
|
15
|
+
if (this.client)
|
|
16
|
+
return;
|
|
15
17
|
let lastError;
|
|
16
18
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
17
19
|
try {
|
|
@@ -20,17 +22,19 @@ export class CdpBridge {
|
|
|
20
22
|
if (!page)
|
|
21
23
|
throw new Error('No page target found among CDP targets');
|
|
22
24
|
this.client = await CDP({ target: page, port: this.port });
|
|
23
|
-
await
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
await Promise.all([
|
|
26
|
+
this.client.Runtime.enable(),
|
|
27
|
+
this.client.DOM.enable(),
|
|
28
|
+
this.client.Page.enable(),
|
|
29
|
+
this.client.Network.enable(),
|
|
30
|
+
]);
|
|
27
31
|
this.client.on('disconnect', () => { this.client = null; });
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
catch (err) {
|
|
31
35
|
lastError = err;
|
|
32
36
|
if (attempt < maxRetries) {
|
|
33
|
-
await new Promise(r => setTimeout(r,
|
|
37
|
+
await new Promise(r => setTimeout(r, 300));
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -5,7 +5,7 @@ import { CdpBridge } from './cdp-bridge.js';
|
|
|
5
5
|
import { buildTools } from './tool-builder.js';
|
|
6
6
|
import { buildResources } from './resource-builder.js';
|
|
7
7
|
import { getCdpTools } from '../cdp-tools/index.js';
|
|
8
|
-
function registerToolHandlers(server, bridge, ipcTools, cdpToolDefs) {
|
|
8
|
+
function registerToolHandlers(server, bridge, ipcTools, cdpToolDefs, customTools) {
|
|
9
9
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
10
10
|
tools: [
|
|
11
11
|
...ipcTools.map(t => ({
|
|
@@ -14,6 +14,11 @@ function registerToolHandlers(server, bridge, ipcTools, cdpToolDefs) {
|
|
|
14
14
|
inputSchema: t.inputSchema,
|
|
15
15
|
})),
|
|
16
16
|
...cdpToolDefs.map(t => t.definition),
|
|
17
|
+
...customTools.map(t => ({
|
|
18
|
+
name: t.name,
|
|
19
|
+
description: t.description,
|
|
20
|
+
inputSchema: t.inputSchema,
|
|
21
|
+
})),
|
|
17
22
|
],
|
|
18
23
|
}));
|
|
19
24
|
const ipcHandlerMap = new Map();
|
|
@@ -24,6 +29,10 @@ function registerToolHandlers(server, bridge, ipcTools, cdpToolDefs) {
|
|
|
24
29
|
for (const tool of cdpToolDefs) {
|
|
25
30
|
cdpHandlerMap.set(tool.definition.name, tool.handler);
|
|
26
31
|
}
|
|
32
|
+
const customHandlerMap = new Map();
|
|
33
|
+
for (const tool of customTools) {
|
|
34
|
+
customHandlerMap.set(tool.name, tool);
|
|
35
|
+
}
|
|
27
36
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
28
37
|
const { name, arguments: args } = request.params;
|
|
29
38
|
const ipcTool = ipcHandlerMap.get(name);
|
|
@@ -59,6 +68,18 @@ function registerToolHandlers(server, bridge, ipcTools, cdpToolDefs) {
|
|
|
59
68
|
};
|
|
60
69
|
}
|
|
61
70
|
}
|
|
71
|
+
const customTool = customHandlerMap.get(name);
|
|
72
|
+
if (customTool) {
|
|
73
|
+
try {
|
|
74
|
+
return await customTool.handler((args || {}));
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
62
83
|
return {
|
|
63
84
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
64
85
|
isError: true,
|
|
@@ -107,12 +128,21 @@ export async function startServer(config) {
|
|
|
107
128
|
cdpToolDefs = cdpToolDefs.filter(t => allowed.has(t.definition.name));
|
|
108
129
|
}
|
|
109
130
|
}
|
|
131
|
+
if (config.cdpTools) {
|
|
132
|
+
try {
|
|
133
|
+
await bridge.connect();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// App may not be running yet — tools will prompt to connect
|
|
137
|
+
}
|
|
138
|
+
}
|
|
110
139
|
const resources = buildResources(config);
|
|
111
|
-
const server = new Server({ name: config.app.name, version: '0.
|
|
140
|
+
const server = new Server({ name: config.app.name, version: '0.2.0' }, { capabilities: {
|
|
112
141
|
tools: {},
|
|
113
142
|
...(resources.length > 0 ? { resources: {} } : {}),
|
|
114
143
|
} });
|
|
115
|
-
|
|
144
|
+
const customTools = config.customTools || [];
|
|
145
|
+
registerToolHandlers(server, bridge, ipcTools, cdpToolDefs, customTools);
|
|
116
146
|
registerResourceHandlers(server, bridge, resources);
|
|
117
147
|
const cleanup = async () => {
|
|
118
148
|
await bridge.close();
|
package/package.json
CHANGED