@gtkx/mcp 0.18.1 → 0.18.2
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/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -0
- package/dist/connection-manager.d.ts +1 -0
- package/dist/connection-manager.d.ts.map +1 -0
- package/dist/connection-manager.js +1 -0
- package/dist/connection-manager.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/errors.d.ts +1 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/types.d.ts +1 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +1 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/socket-server.d.ts +1 -0
- package/dist/socket-server.d.ts.map +1 -0
- package/dist/socket-server.js +1 -0
- package/dist/socket-server.js.map +1 -0
- package/package.json +4 -2
- package/src/cli.ts +449 -0
- package/src/connection-manager.ts +247 -0
- package/src/index.ts +27 -0
- package/src/protocol/errors.ts +128 -0
- package/src/protocol/types.ts +153 -0
- package/src/socket-server.ts +194 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import {
|
|
3
|
+
appNotFoundError,
|
|
4
|
+
invalidRequestError,
|
|
5
|
+
ipcTimeoutError,
|
|
6
|
+
McpError,
|
|
7
|
+
type McpErrorCode,
|
|
8
|
+
noAppConnectedError,
|
|
9
|
+
} from "./protocol/errors.js";
|
|
10
|
+
import { type AppInfo, type IpcRequest, type IpcResponse, RegisterParamsSchema } from "./protocol/types.js";
|
|
11
|
+
import type { AppConnection, SocketServer } from "./socket-server.js";
|
|
12
|
+
|
|
13
|
+
type ConnectionManagerEventMap = {
|
|
14
|
+
appRegistered: [AppInfo];
|
|
15
|
+
appUnregistered: [string];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type PendingRequest = {
|
|
19
|
+
resolve: (result: unknown) => void;
|
|
20
|
+
reject: (error: Error) => void;
|
|
21
|
+
timeout: NodeJS.Timeout;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type RegisteredApp = {
|
|
25
|
+
info: AppInfo;
|
|
26
|
+
connection: AppConnection;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Manages connections between the MCP server and GTKX applications.
|
|
31
|
+
*
|
|
32
|
+
* Handles app registration, request routing, and connection lifecycle.
|
|
33
|
+
*/
|
|
34
|
+
export class ConnectionManager extends EventEmitter<ConnectionManagerEventMap> {
|
|
35
|
+
private static readonly DEFAULT_WAIT_TIMEOUT = 10000;
|
|
36
|
+
|
|
37
|
+
private apps: Map<string, RegisteredApp> = new Map();
|
|
38
|
+
private connectionToApp: Map<string, string> = new Map();
|
|
39
|
+
private pendingRequests: Map<string, PendingRequest> = new Map();
|
|
40
|
+
private requestTimeout: number;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private socketServer: SocketServer,
|
|
44
|
+
options: { requestTimeout?: number } = {},
|
|
45
|
+
) {
|
|
46
|
+
super();
|
|
47
|
+
this.requestTimeout = options.requestTimeout ?? 30000;
|
|
48
|
+
|
|
49
|
+
this.socketServer.on("request", (connection, request) => {
|
|
50
|
+
this.handleRequest(connection, request);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.socketServer.on("response", (_connection, response) => {
|
|
54
|
+
this.handleResponse(response);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.socketServer.on("disconnection", (connection) => {
|
|
58
|
+
this.handleDisconnection(connection);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getApps(): AppInfo[] {
|
|
63
|
+
return Array.from(this.apps.values()).map((app) => app.info);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getApp(appId: string): AppInfo | undefined {
|
|
67
|
+
return this.apps.get(appId)?.info;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
hasConnectedApps(): boolean {
|
|
71
|
+
return this.apps.size > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getDefaultApp(): RegisteredApp | undefined {
|
|
75
|
+
const first = this.apps.values().next();
|
|
76
|
+
return first.done ? undefined : first.value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Waits for at least one app to connect and register.
|
|
81
|
+
*
|
|
82
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 10000)
|
|
83
|
+
* @returns Promise that resolves with the first registered app info
|
|
84
|
+
* @throws Error if timeout is reached before any app registers
|
|
85
|
+
*/
|
|
86
|
+
waitForApp(timeout: number = ConnectionManager.DEFAULT_WAIT_TIMEOUT): Promise<AppInfo> {
|
|
87
|
+
const defaultApp = this.getDefaultApp();
|
|
88
|
+
if (defaultApp) {
|
|
89
|
+
return Promise.resolve(defaultApp.info);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const timeoutId = setTimeout(() => {
|
|
94
|
+
this.off("appRegistered", onRegister);
|
|
95
|
+
reject(
|
|
96
|
+
new Error(
|
|
97
|
+
`Timeout waiting for app registration after ${timeout}ms. ` +
|
|
98
|
+
"Make sure your GTKX app is running with 'gtkx dev'.",
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
}, timeout);
|
|
102
|
+
|
|
103
|
+
const onRegister = (appInfo: AppInfo) => {
|
|
104
|
+
clearTimeout(timeoutId);
|
|
105
|
+
this.off("appRegistered", onRegister);
|
|
106
|
+
resolve(appInfo);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.on("appRegistered", onRegister);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async sendToApp<T>(appId: string | undefined, method: string, params?: unknown): Promise<T> {
|
|
114
|
+
const app = appId ? this.apps.get(appId) : this.getDefaultApp();
|
|
115
|
+
|
|
116
|
+
if (!app) {
|
|
117
|
+
if (appId) {
|
|
118
|
+
throw appNotFoundError(appId);
|
|
119
|
+
}
|
|
120
|
+
throw noAppConnectedError();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const requestId = crypto.randomUUID();
|
|
124
|
+
const request: IpcRequest = {
|
|
125
|
+
id: requestId,
|
|
126
|
+
method,
|
|
127
|
+
params,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return new Promise<T>((resolve, reject) => {
|
|
131
|
+
const timeout = setTimeout(() => {
|
|
132
|
+
this.pendingRequests.delete(requestId);
|
|
133
|
+
reject(ipcTimeoutError(this.requestTimeout));
|
|
134
|
+
}, this.requestTimeout);
|
|
135
|
+
|
|
136
|
+
this.pendingRequests.set(requestId, {
|
|
137
|
+
resolve: resolve as (result: unknown) => void,
|
|
138
|
+
reject,
|
|
139
|
+
timeout,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const sent = this.socketServer.send(app.connection.id, request);
|
|
143
|
+
if (!sent) {
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
this.pendingRequests.delete(requestId);
|
|
146
|
+
reject(appNotFoundError(app.info.appId));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cleanup(): void {
|
|
153
|
+
for (const pending of this.pendingRequests.values()) {
|
|
154
|
+
clearTimeout(pending.timeout);
|
|
155
|
+
pending.reject(new Error("Connection manager shutting down"));
|
|
156
|
+
}
|
|
157
|
+
this.pendingRequests.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private handleRequest(connection: AppConnection, request: IpcRequest): void {
|
|
161
|
+
if (request.method === "app.register") {
|
|
162
|
+
this.handleRegister(connection, request);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (request.method === "app.unregister") {
|
|
167
|
+
this.handleUnregister(connection, request);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private handleResponse(response: IpcResponse): void {
|
|
173
|
+
const pending = this.pendingRequests.get(response.id);
|
|
174
|
+
if (!pending) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
clearTimeout(pending.timeout);
|
|
179
|
+
this.pendingRequests.delete(response.id);
|
|
180
|
+
|
|
181
|
+
if (response.error) {
|
|
182
|
+
const err = response.error;
|
|
183
|
+
pending.reject(new McpError(err.code as McpErrorCode, err.message, err.data));
|
|
184
|
+
} else {
|
|
185
|
+
pending.resolve(response.result);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private handleRegister(connection: AppConnection, request: IpcRequest): void {
|
|
190
|
+
const parseResult = RegisterParamsSchema.safeParse(request.params);
|
|
191
|
+
if (!parseResult.success) {
|
|
192
|
+
const response: IpcResponse = {
|
|
193
|
+
id: request.id,
|
|
194
|
+
error: invalidRequestError(parseResult.error.message).toIpcError(),
|
|
195
|
+
};
|
|
196
|
+
this.socketServer.send(connection.id, response);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const params = parseResult.data;
|
|
201
|
+
const appInfo: AppInfo = {
|
|
202
|
+
appId: params.appId,
|
|
203
|
+
pid: params.pid,
|
|
204
|
+
windows: [],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const registeredApp: RegisteredApp = {
|
|
208
|
+
info: appInfo,
|
|
209
|
+
connection,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
this.apps.set(params.appId, registeredApp);
|
|
213
|
+
this.connectionToApp.set(connection.id, params.appId);
|
|
214
|
+
|
|
215
|
+
const response: IpcResponse = {
|
|
216
|
+
id: request.id,
|
|
217
|
+
result: { success: true },
|
|
218
|
+
};
|
|
219
|
+
this.socketServer.send(connection.id, response);
|
|
220
|
+
|
|
221
|
+
this.emit("appRegistered", appInfo);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private handleUnregister(connection: AppConnection, request: IpcRequest): void {
|
|
225
|
+
const appId = this.connectionToApp.get(connection.id);
|
|
226
|
+
if (appId) {
|
|
227
|
+
this.apps.delete(appId);
|
|
228
|
+
this.connectionToApp.delete(connection.id);
|
|
229
|
+
this.emit("appUnregistered", appId);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const response: IpcResponse = {
|
|
233
|
+
id: request.id,
|
|
234
|
+
result: { success: true },
|
|
235
|
+
};
|
|
236
|
+
this.socketServer.send(connection.id, response);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private handleDisconnection(connection: AppConnection): void {
|
|
240
|
+
const appId = this.connectionToApp.get(connection.id);
|
|
241
|
+
if (appId) {
|
|
242
|
+
this.apps.delete(appId);
|
|
243
|
+
this.connectionToApp.delete(connection.id);
|
|
244
|
+
this.emit("appUnregistered", appId);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { ConnectionManager } from "./connection-manager.js";
|
|
2
|
+
export {
|
|
3
|
+
appNotFoundError,
|
|
4
|
+
invalidRequestError,
|
|
5
|
+
ipcTimeoutError,
|
|
6
|
+
McpError,
|
|
7
|
+
McpErrorCode,
|
|
8
|
+
methodNotFoundError,
|
|
9
|
+
noAppConnectedError,
|
|
10
|
+
widgetNotFoundError,
|
|
11
|
+
} from "./protocol/errors.js";
|
|
12
|
+
export {
|
|
13
|
+
type AppInfo,
|
|
14
|
+
DEFAULT_SOCKET_PATH,
|
|
15
|
+
getRuntimeDir,
|
|
16
|
+
type IpcError,
|
|
17
|
+
IpcErrorSchema,
|
|
18
|
+
type IpcMessage,
|
|
19
|
+
type IpcMethod,
|
|
20
|
+
type IpcRequest,
|
|
21
|
+
IpcRequestSchema,
|
|
22
|
+
type IpcResponse,
|
|
23
|
+
IpcResponseSchema,
|
|
24
|
+
type QueryOptions,
|
|
25
|
+
type SerializedWidget,
|
|
26
|
+
} from "./protocol/types.js";
|
|
27
|
+
export { type AppConnection, SocketServer } from "./socket-server.js";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for MCP protocol errors.
|
|
3
|
+
*/
|
|
4
|
+
export enum McpErrorCode {
|
|
5
|
+
/** Internal server error */
|
|
6
|
+
INTERNAL_ERROR = 1000,
|
|
7
|
+
/** No GTKX application is connected */
|
|
8
|
+
NO_APP_CONNECTED = 1001,
|
|
9
|
+
/** Requested application ID was not found */
|
|
10
|
+
APP_NOT_FOUND = 1002,
|
|
11
|
+
/** Widget with specified ID was not found */
|
|
12
|
+
WIDGET_NOT_FOUND = 1003,
|
|
13
|
+
/** Widget cannot be interacted with */
|
|
14
|
+
WIDGET_NOT_INTERACTABLE = 1004,
|
|
15
|
+
/** Query timed out waiting for widget */
|
|
16
|
+
QUERY_TIMEOUT = 1005,
|
|
17
|
+
/** Widget is not the expected type */
|
|
18
|
+
INVALID_WIDGET_TYPE = 1006,
|
|
19
|
+
/** Screenshot capture failed */
|
|
20
|
+
SCREENSHOT_FAILED = 1007,
|
|
21
|
+
/** IPC request timed out */
|
|
22
|
+
IPC_TIMEOUT = 1008,
|
|
23
|
+
/** Failed to serialize data */
|
|
24
|
+
SERIALIZATION_ERROR = 1009,
|
|
25
|
+
/** Request format is invalid */
|
|
26
|
+
INVALID_REQUEST = 1010,
|
|
27
|
+
/** Requested method does not exist */
|
|
28
|
+
METHOD_NOT_FOUND = 1011,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Error class for MCP protocol errors.
|
|
33
|
+
*
|
|
34
|
+
* Contains an error code, message, and optional additional data.
|
|
35
|
+
*/
|
|
36
|
+
export class McpError extends Error {
|
|
37
|
+
/** The MCP error code */
|
|
38
|
+
readonly code: McpErrorCode;
|
|
39
|
+
/** Additional error context */
|
|
40
|
+
readonly data?: unknown;
|
|
41
|
+
|
|
42
|
+
constructor(code: McpErrorCode, message: string, data?: unknown) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.code = code;
|
|
45
|
+
this.data = data;
|
|
46
|
+
this.name = "McpError";
|
|
47
|
+
|
|
48
|
+
if (Error.captureStackTrace) {
|
|
49
|
+
Error.captureStackTrace(this, McpError);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Converts the error to an IPC-compatible format.
|
|
55
|
+
*
|
|
56
|
+
* @returns Object suitable for IPC response error field
|
|
57
|
+
*/
|
|
58
|
+
toIpcError(): { code: number; message: string; data?: unknown } {
|
|
59
|
+
return {
|
|
60
|
+
code: this.code,
|
|
61
|
+
message: this.message,
|
|
62
|
+
...(this.data !== undefined && { data: this.data }),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates an error for when no GTKX application is connected.
|
|
69
|
+
*
|
|
70
|
+
* @returns McpError with NO_APP_CONNECTED code
|
|
71
|
+
*/
|
|
72
|
+
export function noAppConnectedError(): McpError {
|
|
73
|
+
return new McpError(
|
|
74
|
+
McpErrorCode.NO_APP_CONNECTED,
|
|
75
|
+
"No GTKX application connected. Start an app with 'gtkx dev' to connect.",
|
|
76
|
+
{ hint: "Run 'gtkx dev src/app.tsx' in your project directory" },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates an error for when a requested app is not found.
|
|
82
|
+
*
|
|
83
|
+
* @param appId - The application ID that was not found
|
|
84
|
+
* @returns McpError with APP_NOT_FOUND code
|
|
85
|
+
*/
|
|
86
|
+
export function appNotFoundError(appId: string): McpError {
|
|
87
|
+
return new McpError(McpErrorCode.APP_NOT_FOUND, `Application '${appId}' not found`, { appId });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates an error for when a widget is not found.
|
|
92
|
+
*
|
|
93
|
+
* @param widgetId - The widget ID that was not found
|
|
94
|
+
* @returns McpError with WIDGET_NOT_FOUND code
|
|
95
|
+
*/
|
|
96
|
+
export function widgetNotFoundError(widgetId: string): McpError {
|
|
97
|
+
return new McpError(McpErrorCode.WIDGET_NOT_FOUND, `Widget '${widgetId}' not found`, { widgetId });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates an error for when an IPC request times out.
|
|
102
|
+
*
|
|
103
|
+
* @param timeout - The timeout duration in milliseconds
|
|
104
|
+
* @returns McpError with IPC_TIMEOUT code
|
|
105
|
+
*/
|
|
106
|
+
export function ipcTimeoutError(timeout: number): McpError {
|
|
107
|
+
return new McpError(McpErrorCode.IPC_TIMEOUT, `IPC request timed out after ${timeout}ms`, { timeout });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates an error for invalid request format.
|
|
112
|
+
*
|
|
113
|
+
* @param reason - Description of why the request is invalid
|
|
114
|
+
* @returns McpError with INVALID_REQUEST code
|
|
115
|
+
*/
|
|
116
|
+
export function invalidRequestError(reason: string): McpError {
|
|
117
|
+
return new McpError(McpErrorCode.INVALID_REQUEST, `Invalid request: ${reason}`, { reason });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates an error for when a method is not found.
|
|
122
|
+
*
|
|
123
|
+
* @param method - The method name that was not found
|
|
124
|
+
* @returns McpError with METHOD_NOT_FOUND code
|
|
125
|
+
*/
|
|
126
|
+
export function methodNotFoundError(method: string): McpError {
|
|
127
|
+
return new McpError(McpErrorCode.METHOD_NOT_FOUND, `Method '${method}' not found`, { method });
|
|
128
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for validating IPC requests.
|
|
7
|
+
*/
|
|
8
|
+
export const IpcRequestSchema = z.object({
|
|
9
|
+
id: z.string(),
|
|
10
|
+
method: z.string(),
|
|
11
|
+
params: z.unknown().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An IPC request message.
|
|
16
|
+
*/
|
|
17
|
+
export type IpcRequest = z.infer<typeof IpcRequestSchema>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An IPC error object.
|
|
21
|
+
*/
|
|
22
|
+
export type IpcError = {
|
|
23
|
+
/** Error code */
|
|
24
|
+
code: number;
|
|
25
|
+
/** Error message */
|
|
26
|
+
message: string;
|
|
27
|
+
/** Additional error data */
|
|
28
|
+
data?: unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Zod schema for validating IPC errors.
|
|
33
|
+
*/
|
|
34
|
+
export const IpcErrorSchema = z.object({
|
|
35
|
+
code: z.number(),
|
|
36
|
+
message: z.string(),
|
|
37
|
+
data: z.unknown().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Zod schema for validating IPC responses.
|
|
42
|
+
*/
|
|
43
|
+
export const IpcResponseSchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
result: z.unknown().optional(),
|
|
46
|
+
error: IpcErrorSchema.optional(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An IPC response message.
|
|
51
|
+
*/
|
|
52
|
+
export type IpcResponse = z.infer<typeof IpcResponseSchema>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A serialized representation of a GTK widget for IPC transfer.
|
|
56
|
+
*/
|
|
57
|
+
export type SerializedWidget = {
|
|
58
|
+
/** Unique widget identifier */
|
|
59
|
+
id: string;
|
|
60
|
+
/** Widget type name (e.g., "GtkButton") */
|
|
61
|
+
type: string;
|
|
62
|
+
/** Accessible role */
|
|
63
|
+
role: string;
|
|
64
|
+
/** Widget name (test ID) */
|
|
65
|
+
name: string | null;
|
|
66
|
+
/** Accessible label */
|
|
67
|
+
label: string | null;
|
|
68
|
+
/** Text content */
|
|
69
|
+
text: string | null;
|
|
70
|
+
/** Whether the widget is sensitive (interactive) */
|
|
71
|
+
sensitive: boolean;
|
|
72
|
+
/** Whether the widget is visible */
|
|
73
|
+
visible: boolean;
|
|
74
|
+
/** CSS class names */
|
|
75
|
+
cssClasses: string[];
|
|
76
|
+
/** Child widgets */
|
|
77
|
+
children: SerializedWidget[];
|
|
78
|
+
/** Widget bounds in window coordinates */
|
|
79
|
+
bounds?: {
|
|
80
|
+
x: number;
|
|
81
|
+
y: number;
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Information about a connected GTKX application.
|
|
89
|
+
*/
|
|
90
|
+
export type AppInfo = {
|
|
91
|
+
/** Application ID (e.g., "com.example.myapp") */
|
|
92
|
+
appId: string;
|
|
93
|
+
/** Process ID */
|
|
94
|
+
pid: number;
|
|
95
|
+
/** Open windows */
|
|
96
|
+
windows: Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
title: string | null;
|
|
99
|
+
}>;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Options for widget queries.
|
|
104
|
+
*/
|
|
105
|
+
export type QueryOptions = {
|
|
106
|
+
/** Widget name to match */
|
|
107
|
+
name?: string;
|
|
108
|
+
/** Require exact match */
|
|
109
|
+
exact?: boolean;
|
|
110
|
+
/** Query timeout in milliseconds */
|
|
111
|
+
timeout?: number;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Zod schema for app registration parameters.
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
export const RegisterParamsSchema = z.object({
|
|
119
|
+
appId: z.string(),
|
|
120
|
+
pid: z.number(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Available IPC methods.
|
|
125
|
+
*/
|
|
126
|
+
export type IpcMethod =
|
|
127
|
+
| "app.register"
|
|
128
|
+
| "app.unregister"
|
|
129
|
+
| "app.getWindows"
|
|
130
|
+
| "widget.getTree"
|
|
131
|
+
| "widget.query"
|
|
132
|
+
| "widget.getProps"
|
|
133
|
+
| "widget.click"
|
|
134
|
+
| "widget.type"
|
|
135
|
+
| "widget.fireEvent"
|
|
136
|
+
| "widget.screenshot";
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Union type for any IPC message (request or response).
|
|
140
|
+
*/
|
|
141
|
+
export type IpcMessage = IpcRequest | IpcResponse;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gets the XDG runtime directory or falls back to system temp.
|
|
145
|
+
*
|
|
146
|
+
* @returns Path to the runtime directory
|
|
147
|
+
*/
|
|
148
|
+
export const getRuntimeDir = (): string => process.env.XDG_RUNTIME_DIR ?? tmpdir();
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Default path for the MCP socket file.
|
|
152
|
+
*/
|
|
153
|
+
export const DEFAULT_SOCKET_PATH = join(getRuntimeDir(), "gtkx-mcp.sock");
|