@gtkx/mcp 0.11.1 → 0.12.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 +103 -0
- package/dist/cli.js +46 -27
- package/dist/connection-manager.d.ts +14 -0
- package/dist/connection-manager.js +45 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/protocol/errors.d.ts +62 -0
- package/dist/protocol/errors.js +62 -0
- package/dist/protocol/types.d.ts +114 -8
- package/dist/protocol/types.js +35 -1
- package/dist/socket-server.d.ts +13 -1
- package/dist/socket-server.js +27 -11
- package/package.json +2 -3
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/logo.svg" alt="GTKX" width="60" height="60">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">GTKX</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Build native GTK4 desktop applications with React and TypeScript.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@gtkx/react"><img src="https://img.shields.io/npm/v/@gtkx/react.svg" alt="npm version"></a>
|
|
13
|
+
<a href="https://github.com/eugeniodepalo/gtkx/actions"><img src="https://img.shields.io/github/actions/workflow/status/eugeniodepalo/gtkx/ci.yml" alt="CI"></a>
|
|
14
|
+
<a href="https://github.com/eugeniodepalo/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/eugeniodepalo/gtkx/discussions"><img src="https://img.shields.io/badge/discussions-GitHub-blue" alt="GitHub Discussions"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
GTKX lets you write Linux desktop applications using React. Your components render as native GTK4 widgets through a Rust FFI bridge—no webviews, no Electron, just native performance with the developer experience you already know.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @gtkx/cli create my-app
|
|
26
|
+
cd my-app
|
|
27
|
+
npm run dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import {
|
|
34
|
+
GtkApplicationWindow,
|
|
35
|
+
GtkBox,
|
|
36
|
+
GtkButton,
|
|
37
|
+
GtkLabel,
|
|
38
|
+
quit,
|
|
39
|
+
render,
|
|
40
|
+
} from "@gtkx/react";
|
|
41
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
42
|
+
import { useState } from "react";
|
|
43
|
+
|
|
44
|
+
const App = () => {
|
|
45
|
+
const [count, setCount] = useState(0);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<GtkApplicationWindow
|
|
49
|
+
title="Counter"
|
|
50
|
+
defaultWidth={300}
|
|
51
|
+
defaultHeight={200}
|
|
52
|
+
onCloseRequest={quit}
|
|
53
|
+
>
|
|
54
|
+
<GtkBox
|
|
55
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
56
|
+
spacing={20}
|
|
57
|
+
valign={Gtk.Align.CENTER}
|
|
58
|
+
>
|
|
59
|
+
<GtkLabel label={`Count: ${count}`} cssClasses={["title-1"]} />
|
|
60
|
+
<GtkButton label="Increment" onClicked={() => setCount((c) => c + 1)} />
|
|
61
|
+
</GtkBox>
|
|
62
|
+
</GtkApplicationWindow>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
render(<App />, "com.example.counter");
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **React 19** — Hooks, concurrent features, and the component model you know
|
|
72
|
+
- **Native GTK4 widgets** — Real native controls, not web components in a webview
|
|
73
|
+
- **Adwaita support** — Modern GNOME styling with Libadwaita components
|
|
74
|
+
- **Hot Module Replacement** — Fast refresh during development
|
|
75
|
+
- **TypeScript first** — Full type safety with auto-generated bindings
|
|
76
|
+
- **CSS-in-JS styling** — Familiar styling patterns adapted for GTK
|
|
77
|
+
- **Testing utilities** — Component testing similar to Testing Library
|
|
78
|
+
|
|
79
|
+
## Examples
|
|
80
|
+
|
|
81
|
+
Explore complete applications in the [`examples/`](./examples) directory:
|
|
82
|
+
|
|
83
|
+
- **[gtk-demo](./examples/gtk-demo)** — Full replica of the official GTK demo app
|
|
84
|
+
- **[hello-world](./examples/hello-world)** — Minimal application showing a counter
|
|
85
|
+
- **[todo](./examples/todo)** — Full-featured todo application with Adwaita styling and testing
|
|
86
|
+
- **[deploying](./examples/deploying)** — Example of packaging and distributing a GTKX app
|
|
87
|
+
|
|
88
|
+
## Documentation
|
|
89
|
+
|
|
90
|
+
Visit [https://eugeniodepalo.github.io/gtkx](https://eugeniodepalo.github.io/gtkx/) for the full documentation.
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Contributions are welcome! Please see the [contributing guidelines](./CONTRIBUTING.md) and check out the [good first issues](https://github.com/eugeniodepalo/gtkx/labels/good%20first%20issue).
|
|
95
|
+
|
|
96
|
+
## Community
|
|
97
|
+
|
|
98
|
+
- [GitHub Discussions](https://github.com/eugeniodepalo/gtkx/discussions) — Questions, ideas, and general discussion
|
|
99
|
+
- [Issue Tracker](https://github.com/eugeniodepalo/gtkx/issues) — Bug reports and feature requests
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
[MPL-2.0](./LICENSE)
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { ConnectionManager } from "./connection-manager.js";
|
|
8
|
-
import { noAppConnectedError } from "./protocol/errors.js";
|
|
9
8
|
import { DEFAULT_SOCKET_PATH } from "./protocol/types.js";
|
|
10
9
|
import { SocketServer } from "./socket-server.js";
|
|
11
10
|
const require = createRequire(import.meta.url);
|
|
@@ -13,6 +12,13 @@ const { version } = require("../package.json");
|
|
|
13
12
|
const AppIdSchema = z.object({
|
|
14
13
|
appId: z.string().optional().describe("App ID to query. If not specified, uses the first connected app."),
|
|
15
14
|
});
|
|
15
|
+
const ListAppsInputSchema = z.object({
|
|
16
|
+
waitForApps: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("If true, wait for at least one app to register before returning. Useful when app is still starting."),
|
|
20
|
+
timeout: z.number().optional().describe("Timeout in milliseconds when waitForApps is true (default: 10000)"),
|
|
21
|
+
});
|
|
16
22
|
const GetWidgetTreeInputSchema = AppIdSchema;
|
|
17
23
|
const QueryWidgetsInputSchema = AppIdSchema.extend({
|
|
18
24
|
by: z.enum(["role", "text", "testId", "labelText"]).describe("Query type"),
|
|
@@ -41,7 +47,6 @@ const FireEventInputSchema = WidgetIdSchema.extend({
|
|
|
41
47
|
});
|
|
42
48
|
const TakeScreenshotInputSchema = AppIdSchema.extend({
|
|
43
49
|
windowId: z.string().optional().describe("Window ID to capture. If not specified, captures the first window."),
|
|
44
|
-
format: z.enum(["png", "jpeg"]).optional().describe("Image format (default: png)"),
|
|
45
50
|
});
|
|
46
51
|
const tools = [
|
|
47
52
|
{
|
|
@@ -49,7 +54,16 @@ const tools = [
|
|
|
49
54
|
description: "List all connected GTKX applications",
|
|
50
55
|
inputSchema: {
|
|
51
56
|
type: "object",
|
|
52
|
-
properties: {
|
|
57
|
+
properties: {
|
|
58
|
+
waitForApps: {
|
|
59
|
+
type: "boolean",
|
|
60
|
+
description: "If true, wait for at least one app to register before returning. Useful when app is still starting.",
|
|
61
|
+
},
|
|
62
|
+
timeout: {
|
|
63
|
+
type: "number",
|
|
64
|
+
description: "Timeout in milliseconds when waitForApps is true (default: 10000)",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
53
67
|
required: [],
|
|
54
68
|
},
|
|
55
69
|
},
|
|
@@ -190,7 +204,7 @@ const tools = [
|
|
|
190
204
|
},
|
|
191
205
|
{
|
|
192
206
|
name: "gtkx_take_screenshot",
|
|
193
|
-
description: "Capture a screenshot of a window. Returns base64-encoded image data.",
|
|
207
|
+
description: "Capture a screenshot of a window. Returns base64-encoded PNG image data.",
|
|
194
208
|
inputSchema: {
|
|
195
209
|
type: "object",
|
|
196
210
|
properties: {
|
|
@@ -202,11 +216,6 @@ const tools = [
|
|
|
202
216
|
type: "string",
|
|
203
217
|
description: "Window ID to capture. If not specified, captures the first window.",
|
|
204
218
|
},
|
|
205
|
-
format: {
|
|
206
|
-
type: "string",
|
|
207
|
-
enum: ["png", "jpeg"],
|
|
208
|
-
description: "Image format (default: png)",
|
|
209
|
-
},
|
|
210
219
|
},
|
|
211
220
|
required: [],
|
|
212
221
|
},
|
|
@@ -215,19 +224,20 @@ const tools = [
|
|
|
215
224
|
async function main() {
|
|
216
225
|
const socketServer = new SocketServer(DEFAULT_SOCKET_PATH);
|
|
217
226
|
const connectionManager = new ConnectionManager(socketServer);
|
|
227
|
+
socketServer.on("error", (error) => {
|
|
228
|
+
const code = error.code;
|
|
229
|
+
if (code !== "EPIPE" && code !== "ECONNRESET") {
|
|
230
|
+
console.error("[gtkx] Socket error:", error.message);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
218
233
|
await socketServer.start();
|
|
219
|
-
console.error(`[gtkx
|
|
234
|
+
console.error(`[gtkx] Socket server listening on ${DEFAULT_SOCKET_PATH}`);
|
|
220
235
|
connectionManager.on("appRegistered", (appInfo) => {
|
|
221
|
-
console.error(`[gtkx
|
|
236
|
+
console.error(`[gtkx] App registered: ${appInfo.appId} (PID: ${appInfo.pid})`);
|
|
222
237
|
});
|
|
223
238
|
connectionManager.on("appUnregistered", (appId) => {
|
|
224
|
-
console.error(`[gtkx
|
|
239
|
+
console.error(`[gtkx] App unregistered: ${appId}`);
|
|
225
240
|
});
|
|
226
|
-
const requireConnectedApp = () => {
|
|
227
|
-
if (!connectionManager.hasConnectedApps()) {
|
|
228
|
-
throw noAppConnectedError();
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
241
|
const server = new Server({
|
|
232
242
|
name: "gtkx-mcp",
|
|
233
243
|
version,
|
|
@@ -244,6 +254,23 @@ async function main() {
|
|
|
244
254
|
try {
|
|
245
255
|
switch (name) {
|
|
246
256
|
case "gtkx_list_apps": {
|
|
257
|
+
const input = ListAppsInputSchema.parse(args);
|
|
258
|
+
if (input.waitForApps && !connectionManager.hasConnectedApps()) {
|
|
259
|
+
try {
|
|
260
|
+
await connectionManager.waitForApp(input.timeout);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
return {
|
|
264
|
+
content: [
|
|
265
|
+
{
|
|
266
|
+
type: "text",
|
|
267
|
+
text: error instanceof Error ? error.message : "Timeout waiting for app",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
isError: true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
247
274
|
const apps = connectionManager.getApps();
|
|
248
275
|
return {
|
|
249
276
|
content: [{ type: "text", text: JSON.stringify(apps, null, 2) }],
|
|
@@ -251,15 +278,13 @@ async function main() {
|
|
|
251
278
|
}
|
|
252
279
|
case "gtkx_get_widget_tree": {
|
|
253
280
|
const input = GetWidgetTreeInputSchema.parse(args);
|
|
254
|
-
requireConnectedApp();
|
|
255
281
|
const result = await connectionManager.sendToApp(input.appId, "widget.getTree", {});
|
|
256
282
|
return {
|
|
257
|
-
content: [{ type: "text", text:
|
|
283
|
+
content: [{ type: "text", text: result.tree }],
|
|
258
284
|
};
|
|
259
285
|
}
|
|
260
286
|
case "gtkx_query_widgets": {
|
|
261
287
|
const input = QueryWidgetsInputSchema.parse(args);
|
|
262
|
-
requireConnectedApp();
|
|
263
288
|
const result = await connectionManager.sendToApp(input.appId, "widget.query", {
|
|
264
289
|
queryType: input.by,
|
|
265
290
|
value: input.value,
|
|
@@ -271,7 +296,6 @@ async function main() {
|
|
|
271
296
|
}
|
|
272
297
|
case "gtkx_get_widget_props": {
|
|
273
298
|
const input = GetWidgetPropsInputSchema.parse(args);
|
|
274
|
-
requireConnectedApp();
|
|
275
299
|
const result = await connectionManager.sendToApp(input.appId, "widget.getProps", {
|
|
276
300
|
widgetId: input.widgetId,
|
|
277
301
|
});
|
|
@@ -281,7 +305,6 @@ async function main() {
|
|
|
281
305
|
}
|
|
282
306
|
case "gtkx_click": {
|
|
283
307
|
const input = ClickInputSchema.parse(args);
|
|
284
|
-
requireConnectedApp();
|
|
285
308
|
await connectionManager.sendToApp(input.appId, "widget.click", {
|
|
286
309
|
widgetId: input.widgetId,
|
|
287
310
|
});
|
|
@@ -291,7 +314,6 @@ async function main() {
|
|
|
291
314
|
}
|
|
292
315
|
case "gtkx_type": {
|
|
293
316
|
const input = TypeInputSchema.parse(args);
|
|
294
|
-
requireConnectedApp();
|
|
295
317
|
await connectionManager.sendToApp(input.appId, "widget.type", {
|
|
296
318
|
widgetId: input.widgetId,
|
|
297
319
|
text: input.text,
|
|
@@ -303,7 +325,6 @@ async function main() {
|
|
|
303
325
|
}
|
|
304
326
|
case "gtkx_fire_event": {
|
|
305
327
|
const input = FireEventInputSchema.parse(args);
|
|
306
|
-
requireConnectedApp();
|
|
307
328
|
await connectionManager.sendToApp(input.appId, "widget.fireEvent", {
|
|
308
329
|
widgetId: input.widgetId,
|
|
309
330
|
signal: input.signal,
|
|
@@ -315,10 +336,8 @@ async function main() {
|
|
|
315
336
|
}
|
|
316
337
|
case "gtkx_take_screenshot": {
|
|
317
338
|
const input = TakeScreenshotInputSchema.parse(args);
|
|
318
|
-
requireConnectedApp();
|
|
319
339
|
const result = await connectionManager.sendToApp(input.appId, "widget.screenshot", {
|
|
320
340
|
windowId: input.windowId,
|
|
321
|
-
format: input.format,
|
|
322
341
|
});
|
|
323
342
|
return {
|
|
324
343
|
content: [
|
|
@@ -365,6 +384,6 @@ async function main() {
|
|
|
365
384
|
process.on("SIGTERM", shutdown);
|
|
366
385
|
}
|
|
367
386
|
main().catch((error) => {
|
|
368
|
-
console.error("[gtkx
|
|
387
|
+
console.error("[gtkx] Fatal error:", error);
|
|
369
388
|
process.exit(1);
|
|
370
389
|
});
|
|
@@ -9,8 +9,14 @@ interface RegisteredApp {
|
|
|
9
9
|
info: AppInfo;
|
|
10
10
|
connection: AppConnection;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Manages connections between the MCP server and GTKX applications.
|
|
14
|
+
*
|
|
15
|
+
* Handles app registration, request routing, and connection lifecycle.
|
|
16
|
+
*/
|
|
12
17
|
export declare class ConnectionManager extends EventEmitter<ConnectionManagerEventMap> {
|
|
13
18
|
private socketServer;
|
|
19
|
+
private static readonly DEFAULT_WAIT_TIMEOUT;
|
|
14
20
|
private apps;
|
|
15
21
|
private connectionToApp;
|
|
16
22
|
private pendingRequests;
|
|
@@ -22,6 +28,14 @@ export declare class ConnectionManager extends EventEmitter<ConnectionManagerEve
|
|
|
22
28
|
getApp(appId: string): AppInfo | undefined;
|
|
23
29
|
hasConnectedApps(): boolean;
|
|
24
30
|
getDefaultApp(): RegisteredApp | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Waits for at least one app to connect and register.
|
|
33
|
+
*
|
|
34
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 10000)
|
|
35
|
+
* @returns Promise that resolves with the first registered app info
|
|
36
|
+
* @throws Error if timeout is reached before any app registers
|
|
37
|
+
*/
|
|
38
|
+
waitForApp(timeout?: number): Promise<AppInfo>;
|
|
25
39
|
sendToApp<T>(appId: string | undefined, method: string, params?: unknown): Promise<T>;
|
|
26
40
|
cleanup(): void;
|
|
27
41
|
private handleRequest;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import { appNotFoundError, invalidRequestError, ipcTimeoutError, McpError, noAppConnectedError, } from "./protocol/errors.js";
|
|
3
3
|
import { RegisterParamsSchema } from "./protocol/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Manages connections between the MCP server and GTKX applications.
|
|
6
|
+
*
|
|
7
|
+
* Handles app registration, request routing, and connection lifecycle.
|
|
8
|
+
*/
|
|
4
9
|
export class ConnectionManager extends EventEmitter {
|
|
5
10
|
socketServer;
|
|
11
|
+
static DEFAULT_WAIT_TIMEOUT = 10000;
|
|
6
12
|
apps = new Map();
|
|
7
13
|
connectionToApp = new Map();
|
|
8
14
|
pendingRequests = new Map();
|
|
@@ -14,6 +20,9 @@ export class ConnectionManager extends EventEmitter {
|
|
|
14
20
|
this.socketServer.on("request", (connection, request) => {
|
|
15
21
|
this.handleRequest(connection, request);
|
|
16
22
|
});
|
|
23
|
+
this.socketServer.on("response", (_connection, response) => {
|
|
24
|
+
this.handleResponse(response);
|
|
25
|
+
});
|
|
17
26
|
this.socketServer.on("disconnection", (connection) => {
|
|
18
27
|
this.handleDisconnection(connection);
|
|
19
28
|
});
|
|
@@ -31,6 +40,32 @@ export class ConnectionManager extends EventEmitter {
|
|
|
31
40
|
const first = this.apps.values().next();
|
|
32
41
|
return first.done ? undefined : first.value;
|
|
33
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Waits for at least one app to connect and register.
|
|
45
|
+
*
|
|
46
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 10000)
|
|
47
|
+
* @returns Promise that resolves with the first registered app info
|
|
48
|
+
* @throws Error if timeout is reached before any app registers
|
|
49
|
+
*/
|
|
50
|
+
waitForApp(timeout = ConnectionManager.DEFAULT_WAIT_TIMEOUT) {
|
|
51
|
+
const defaultApp = this.getDefaultApp();
|
|
52
|
+
if (defaultApp) {
|
|
53
|
+
return Promise.resolve(defaultApp.info);
|
|
54
|
+
}
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const timeoutId = setTimeout(() => {
|
|
57
|
+
this.off("appRegistered", onRegister);
|
|
58
|
+
reject(new Error(`Timeout waiting for app registration after ${timeout}ms. ` +
|
|
59
|
+
"Make sure your GTKX app is running with 'gtkx dev'."));
|
|
60
|
+
}, timeout);
|
|
61
|
+
const onRegister = (appInfo) => {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
this.off("appRegistered", onRegister);
|
|
64
|
+
resolve(appInfo);
|
|
65
|
+
};
|
|
66
|
+
this.on("appRegistered", onRegister);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
34
69
|
async sendToApp(appId, method, params) {
|
|
35
70
|
const app = appId ? this.apps.get(appId) : this.getDefaultApp();
|
|
36
71
|
if (!app) {
|
|
@@ -55,7 +90,13 @@ export class ConnectionManager extends EventEmitter {
|
|
|
55
90
|
reject,
|
|
56
91
|
timeout,
|
|
57
92
|
});
|
|
58
|
-
this.socketServer.send(app.connection.id, request);
|
|
93
|
+
const sent = this.socketServer.send(app.connection.id, request);
|
|
94
|
+
if (!sent) {
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
this.pendingRequests.delete(requestId);
|
|
97
|
+
reject(appNotFoundError(app.info.appId));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
59
100
|
});
|
|
60
101
|
}
|
|
61
102
|
cleanup() {
|
|
@@ -74,16 +115,14 @@ export class ConnectionManager extends EventEmitter {
|
|
|
74
115
|
this.handleUnregister(connection, request);
|
|
75
116
|
return;
|
|
76
117
|
}
|
|
77
|
-
this.handleResponse(request);
|
|
78
118
|
}
|
|
79
|
-
handleResponse(
|
|
80
|
-
const pending = this.pendingRequests.get(
|
|
119
|
+
handleResponse(response) {
|
|
120
|
+
const pending = this.pendingRequests.get(response.id);
|
|
81
121
|
if (!pending) {
|
|
82
122
|
return;
|
|
83
123
|
}
|
|
84
124
|
clearTimeout(pending.timeout);
|
|
85
|
-
this.pendingRequests.delete(
|
|
86
|
-
const response = message;
|
|
125
|
+
this.pendingRequests.delete(response.id);
|
|
87
126
|
if (response.error) {
|
|
88
127
|
const err = response.error;
|
|
89
128
|
pending.reject(new McpError(err.code, err.message, err.data));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ConnectionManager } from "./connection-manager.js";
|
|
2
|
-
export { McpError, McpErrorCode, methodNotFoundError, widgetNotFoundError, } from "./protocol/errors.js";
|
|
3
|
-
export { type AppInfo, DEFAULT_SOCKET_PATH, type IpcError, type IpcMethod, type IpcRequest, type IpcResponse, type QueryOptions, type SerializedWidget, } from "./protocol/types.js";
|
|
2
|
+
export { appNotFoundError, invalidRequestError, ipcTimeoutError, McpError, McpErrorCode, methodNotFoundError, noAppConnectedError, widgetNotFoundError, } from "./protocol/errors.js";
|
|
3
|
+
export { type AppInfo, DEFAULT_SOCKET_PATH, getRuntimeDir, type IpcError, IpcErrorSchema, type IpcMessage, type IpcMethod, type IpcRequest, IpcRequestSchema, type IpcResponse, IpcResponseSchema, type QueryOptions, type SerializedWidget, } from "./protocol/types.js";
|
|
4
4
|
export { type AppConnection, SocketServer } from "./socket-server.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ConnectionManager } from "./connection-manager.js";
|
|
2
|
-
export { McpError, McpErrorCode, methodNotFoundError, widgetNotFoundError, } from "./protocol/errors.js";
|
|
3
|
-
export { DEFAULT_SOCKET_PATH, } from "./protocol/types.js";
|
|
2
|
+
export { appNotFoundError, invalidRequestError, ipcTimeoutError, McpError, McpErrorCode, methodNotFoundError, noAppConnectedError, widgetNotFoundError, } from "./protocol/errors.js";
|
|
3
|
+
export { DEFAULT_SOCKET_PATH, getRuntimeDir, IpcErrorSchema, IpcRequestSchema, IpcResponseSchema, } from "./protocol/types.js";
|
|
4
4
|
export { SocketServer } from "./socket-server.js";
|
|
@@ -1,30 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for MCP protocol errors.
|
|
3
|
+
*/
|
|
1
4
|
export declare enum McpErrorCode {
|
|
5
|
+
/** Internal server error */
|
|
2
6
|
INTERNAL_ERROR = 1000,
|
|
7
|
+
/** No GTKX application is connected */
|
|
3
8
|
NO_APP_CONNECTED = 1001,
|
|
9
|
+
/** Requested application ID was not found */
|
|
4
10
|
APP_NOT_FOUND = 1002,
|
|
11
|
+
/** Widget with specified ID was not found */
|
|
5
12
|
WIDGET_NOT_FOUND = 1003,
|
|
13
|
+
/** Widget cannot be interacted with */
|
|
6
14
|
WIDGET_NOT_INTERACTABLE = 1004,
|
|
15
|
+
/** Query timed out waiting for widget */
|
|
7
16
|
QUERY_TIMEOUT = 1005,
|
|
17
|
+
/** Widget is not the expected type */
|
|
8
18
|
INVALID_WIDGET_TYPE = 1006,
|
|
19
|
+
/** Screenshot capture failed */
|
|
9
20
|
SCREENSHOT_FAILED = 1007,
|
|
21
|
+
/** IPC request timed out */
|
|
10
22
|
IPC_TIMEOUT = 1008,
|
|
23
|
+
/** Failed to serialize data */
|
|
11
24
|
SERIALIZATION_ERROR = 1009,
|
|
25
|
+
/** Request format is invalid */
|
|
12
26
|
INVALID_REQUEST = 1010,
|
|
27
|
+
/** Requested method does not exist */
|
|
13
28
|
METHOD_NOT_FOUND = 1011
|
|
14
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Error class for MCP protocol errors.
|
|
32
|
+
*
|
|
33
|
+
* Contains an error code, message, and optional additional data.
|
|
34
|
+
*/
|
|
15
35
|
export declare class McpError extends Error {
|
|
36
|
+
/** The MCP error code */
|
|
16
37
|
readonly code: McpErrorCode;
|
|
38
|
+
/** Additional error context */
|
|
17
39
|
readonly data?: unknown;
|
|
18
40
|
constructor(code: McpErrorCode, message: string, data?: unknown);
|
|
41
|
+
/**
|
|
42
|
+
* Converts the error to an IPC-compatible format.
|
|
43
|
+
*
|
|
44
|
+
* @returns Object suitable for IPC response error field
|
|
45
|
+
*/
|
|
19
46
|
toIpcError(): {
|
|
20
47
|
code: number;
|
|
21
48
|
message: string;
|
|
22
49
|
data?: unknown;
|
|
23
50
|
};
|
|
24
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates an error for when no GTKX application is connected.
|
|
54
|
+
*
|
|
55
|
+
* @returns McpError with NO_APP_CONNECTED code
|
|
56
|
+
*/
|
|
25
57
|
export declare function noAppConnectedError(): McpError;
|
|
58
|
+
/**
|
|
59
|
+
* Creates an error for when a requested app is not found.
|
|
60
|
+
*
|
|
61
|
+
* @param appId - The application ID that was not found
|
|
62
|
+
* @returns McpError with APP_NOT_FOUND code
|
|
63
|
+
*/
|
|
26
64
|
export declare function appNotFoundError(appId: string): McpError;
|
|
65
|
+
/**
|
|
66
|
+
* Creates an error for when a widget is not found.
|
|
67
|
+
*
|
|
68
|
+
* @param widgetId - The widget ID that was not found
|
|
69
|
+
* @returns McpError with WIDGET_NOT_FOUND code
|
|
70
|
+
*/
|
|
27
71
|
export declare function widgetNotFoundError(widgetId: string): McpError;
|
|
72
|
+
/**
|
|
73
|
+
* Creates an error for when an IPC request times out.
|
|
74
|
+
*
|
|
75
|
+
* @param timeout - The timeout duration in milliseconds
|
|
76
|
+
* @returns McpError with IPC_TIMEOUT code
|
|
77
|
+
*/
|
|
28
78
|
export declare function ipcTimeoutError(timeout: number): McpError;
|
|
79
|
+
/**
|
|
80
|
+
* Creates an error for invalid request format.
|
|
81
|
+
*
|
|
82
|
+
* @param reason - Description of why the request is invalid
|
|
83
|
+
* @returns McpError with INVALID_REQUEST code
|
|
84
|
+
*/
|
|
29
85
|
export declare function invalidRequestError(reason: string): McpError;
|
|
86
|
+
/**
|
|
87
|
+
* Creates an error for when a method is not found.
|
|
88
|
+
*
|
|
89
|
+
* @param method - The method name that was not found
|
|
90
|
+
* @returns McpError with METHOD_NOT_FOUND code
|
|
91
|
+
*/
|
|
30
92
|
export declare function methodNotFoundError(method: string): McpError;
|
package/dist/protocol/errors.js
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for MCP protocol errors.
|
|
3
|
+
*/
|
|
1
4
|
export var McpErrorCode;
|
|
2
5
|
(function (McpErrorCode) {
|
|
6
|
+
/** Internal server error */
|
|
3
7
|
McpErrorCode[McpErrorCode["INTERNAL_ERROR"] = 1000] = "INTERNAL_ERROR";
|
|
8
|
+
/** No GTKX application is connected */
|
|
4
9
|
McpErrorCode[McpErrorCode["NO_APP_CONNECTED"] = 1001] = "NO_APP_CONNECTED";
|
|
10
|
+
/** Requested application ID was not found */
|
|
5
11
|
McpErrorCode[McpErrorCode["APP_NOT_FOUND"] = 1002] = "APP_NOT_FOUND";
|
|
12
|
+
/** Widget with specified ID was not found */
|
|
6
13
|
McpErrorCode[McpErrorCode["WIDGET_NOT_FOUND"] = 1003] = "WIDGET_NOT_FOUND";
|
|
14
|
+
/** Widget cannot be interacted with */
|
|
7
15
|
McpErrorCode[McpErrorCode["WIDGET_NOT_INTERACTABLE"] = 1004] = "WIDGET_NOT_INTERACTABLE";
|
|
16
|
+
/** Query timed out waiting for widget */
|
|
8
17
|
McpErrorCode[McpErrorCode["QUERY_TIMEOUT"] = 1005] = "QUERY_TIMEOUT";
|
|
18
|
+
/** Widget is not the expected type */
|
|
9
19
|
McpErrorCode[McpErrorCode["INVALID_WIDGET_TYPE"] = 1006] = "INVALID_WIDGET_TYPE";
|
|
20
|
+
/** Screenshot capture failed */
|
|
10
21
|
McpErrorCode[McpErrorCode["SCREENSHOT_FAILED"] = 1007] = "SCREENSHOT_FAILED";
|
|
22
|
+
/** IPC request timed out */
|
|
11
23
|
McpErrorCode[McpErrorCode["IPC_TIMEOUT"] = 1008] = "IPC_TIMEOUT";
|
|
24
|
+
/** Failed to serialize data */
|
|
12
25
|
McpErrorCode[McpErrorCode["SERIALIZATION_ERROR"] = 1009] = "SERIALIZATION_ERROR";
|
|
26
|
+
/** Request format is invalid */
|
|
13
27
|
McpErrorCode[McpErrorCode["INVALID_REQUEST"] = 1010] = "INVALID_REQUEST";
|
|
28
|
+
/** Requested method does not exist */
|
|
14
29
|
McpErrorCode[McpErrorCode["METHOD_NOT_FOUND"] = 1011] = "METHOD_NOT_FOUND";
|
|
15
30
|
})(McpErrorCode || (McpErrorCode = {}));
|
|
31
|
+
/**
|
|
32
|
+
* Error class for MCP protocol errors.
|
|
33
|
+
*
|
|
34
|
+
* Contains an error code, message, and optional additional data.
|
|
35
|
+
*/
|
|
16
36
|
export class McpError extends Error {
|
|
37
|
+
/** The MCP error code */
|
|
17
38
|
code;
|
|
39
|
+
/** Additional error context */
|
|
18
40
|
data;
|
|
19
41
|
constructor(code, message, data) {
|
|
20
42
|
super(message);
|
|
@@ -25,6 +47,11 @@ export class McpError extends Error {
|
|
|
25
47
|
Error.captureStackTrace(this, McpError);
|
|
26
48
|
}
|
|
27
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts the error to an IPC-compatible format.
|
|
52
|
+
*
|
|
53
|
+
* @returns Object suitable for IPC response error field
|
|
54
|
+
*/
|
|
28
55
|
toIpcError() {
|
|
29
56
|
return {
|
|
30
57
|
code: this.code,
|
|
@@ -33,21 +60,56 @@ export class McpError extends Error {
|
|
|
33
60
|
};
|
|
34
61
|
}
|
|
35
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates an error for when no GTKX application is connected.
|
|
65
|
+
*
|
|
66
|
+
* @returns McpError with NO_APP_CONNECTED code
|
|
67
|
+
*/
|
|
36
68
|
export function noAppConnectedError() {
|
|
37
69
|
return new McpError(McpErrorCode.NO_APP_CONNECTED, "No GTKX application connected. Start an app with 'gtkx dev' to connect.", { hint: "Run 'gtkx dev src/app.tsx' in your project directory" });
|
|
38
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Creates an error for when a requested app is not found.
|
|
73
|
+
*
|
|
74
|
+
* @param appId - The application ID that was not found
|
|
75
|
+
* @returns McpError with APP_NOT_FOUND code
|
|
76
|
+
*/
|
|
39
77
|
export function appNotFoundError(appId) {
|
|
40
78
|
return new McpError(McpErrorCode.APP_NOT_FOUND, `Application '${appId}' not found`, { appId });
|
|
41
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Creates an error for when a widget is not found.
|
|
82
|
+
*
|
|
83
|
+
* @param widgetId - The widget ID that was not found
|
|
84
|
+
* @returns McpError with WIDGET_NOT_FOUND code
|
|
85
|
+
*/
|
|
42
86
|
export function widgetNotFoundError(widgetId) {
|
|
43
87
|
return new McpError(McpErrorCode.WIDGET_NOT_FOUND, `Widget '${widgetId}' not found`, { widgetId });
|
|
44
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates an error for when an IPC request times out.
|
|
91
|
+
*
|
|
92
|
+
* @param timeout - The timeout duration in milliseconds
|
|
93
|
+
* @returns McpError with IPC_TIMEOUT code
|
|
94
|
+
*/
|
|
45
95
|
export function ipcTimeoutError(timeout) {
|
|
46
96
|
return new McpError(McpErrorCode.IPC_TIMEOUT, `IPC request timed out after ${timeout}ms`, { timeout });
|
|
47
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Creates an error for invalid request format.
|
|
100
|
+
*
|
|
101
|
+
* @param reason - Description of why the request is invalid
|
|
102
|
+
* @returns McpError with INVALID_REQUEST code
|
|
103
|
+
*/
|
|
48
104
|
export function invalidRequestError(reason) {
|
|
49
105
|
return new McpError(McpErrorCode.INVALID_REQUEST, `Invalid request: ${reason}`, { reason });
|
|
50
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates an error for when a method is not found.
|
|
109
|
+
*
|
|
110
|
+
* @param method - The method name that was not found
|
|
111
|
+
* @returns McpError with METHOD_NOT_FOUND code
|
|
112
|
+
*/
|
|
51
113
|
export function methodNotFoundError(method) {
|
|
52
114
|
return new McpError(McpErrorCode.METHOD_NOT_FOUND, `Method '${method}' not found`, { method });
|
|
53
115
|
}
|
package/dist/protocol/types.d.ts
CHANGED
|
@@ -1,39 +1,116 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for validating IPC requests.
|
|
4
|
+
*/
|
|
2
5
|
export declare const IpcRequestSchema: z.ZodObject<{
|
|
3
6
|
id: z.ZodString;
|
|
4
7
|
method: z.ZodString;
|
|
5
8
|
params: z.ZodOptional<z.ZodUnknown>;
|
|
6
9
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
method: string;
|
|
8
10
|
id: string;
|
|
11
|
+
method: string;
|
|
9
12
|
params?: unknown;
|
|
10
13
|
}, {
|
|
11
|
-
method: string;
|
|
12
14
|
id: string;
|
|
15
|
+
method: string;
|
|
13
16
|
params?: unknown;
|
|
14
17
|
}>;
|
|
18
|
+
/**
|
|
19
|
+
* An IPC request message.
|
|
20
|
+
*/
|
|
15
21
|
export type IpcRequest = z.infer<typeof IpcRequestSchema>;
|
|
22
|
+
/**
|
|
23
|
+
* An IPC error object.
|
|
24
|
+
*/
|
|
16
25
|
export type IpcError = {
|
|
26
|
+
/** Error code */
|
|
17
27
|
code: number;
|
|
28
|
+
/** Error message */
|
|
18
29
|
message: string;
|
|
30
|
+
/** Additional error data */
|
|
19
31
|
data?: unknown;
|
|
20
32
|
};
|
|
21
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Zod schema for validating IPC errors.
|
|
35
|
+
*/
|
|
36
|
+
export declare const IpcErrorSchema: z.ZodObject<{
|
|
37
|
+
code: z.ZodNumber;
|
|
38
|
+
message: z.ZodString;
|
|
39
|
+
data: z.ZodOptional<z.ZodUnknown>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
code: number;
|
|
42
|
+
message: string;
|
|
43
|
+
data?: unknown;
|
|
44
|
+
}, {
|
|
45
|
+
code: number;
|
|
46
|
+
message: string;
|
|
47
|
+
data?: unknown;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Zod schema for validating IPC responses.
|
|
51
|
+
*/
|
|
52
|
+
export declare const IpcResponseSchema: z.ZodObject<{
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
55
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
code: z.ZodNumber;
|
|
57
|
+
message: z.ZodString;
|
|
58
|
+
data: z.ZodOptional<z.ZodUnknown>;
|
|
59
|
+
}, "strip", z.ZodTypeAny, {
|
|
60
|
+
code: number;
|
|
61
|
+
message: string;
|
|
62
|
+
data?: unknown;
|
|
63
|
+
}, {
|
|
64
|
+
code: number;
|
|
65
|
+
message: string;
|
|
66
|
+
data?: unknown;
|
|
67
|
+
}>>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
69
|
id: string;
|
|
23
70
|
result?: unknown;
|
|
24
|
-
error?:
|
|
25
|
-
|
|
71
|
+
error?: {
|
|
72
|
+
code: number;
|
|
73
|
+
message: string;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
} | undefined;
|
|
76
|
+
}, {
|
|
77
|
+
id: string;
|
|
78
|
+
result?: unknown;
|
|
79
|
+
error?: {
|
|
80
|
+
code: number;
|
|
81
|
+
message: string;
|
|
82
|
+
data?: unknown;
|
|
83
|
+
} | undefined;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* An IPC response message.
|
|
87
|
+
*/
|
|
88
|
+
export type IpcResponse = z.infer<typeof IpcResponseSchema>;
|
|
89
|
+
/**
|
|
90
|
+
* A serialized representation of a GTK widget for IPC transfer.
|
|
91
|
+
*/
|
|
26
92
|
export interface SerializedWidget {
|
|
93
|
+
/** Unique widget identifier */
|
|
27
94
|
id: string;
|
|
95
|
+
/** Widget type name (e.g., "GtkButton") */
|
|
28
96
|
type: string;
|
|
97
|
+
/** Accessible role */
|
|
29
98
|
role: string;
|
|
99
|
+
/** Widget name (test ID) */
|
|
30
100
|
name: string | null;
|
|
101
|
+
/** Accessible label */
|
|
31
102
|
label: string | null;
|
|
103
|
+
/** Text content */
|
|
32
104
|
text: string | null;
|
|
105
|
+
/** Whether the widget is sensitive (interactive) */
|
|
33
106
|
sensitive: boolean;
|
|
107
|
+
/** Whether the widget is visible */
|
|
34
108
|
visible: boolean;
|
|
109
|
+
/** CSS class names */
|
|
35
110
|
cssClasses: string[];
|
|
111
|
+
/** Child widgets */
|
|
36
112
|
children: SerializedWidget[];
|
|
113
|
+
/** Widget bounds in window coordinates */
|
|
37
114
|
bounds?: {
|
|
38
115
|
x: number;
|
|
39
116
|
y: number;
|
|
@@ -41,21 +118,35 @@ export interface SerializedWidget {
|
|
|
41
118
|
height: number;
|
|
42
119
|
};
|
|
43
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Information about a connected GTKX application.
|
|
123
|
+
*/
|
|
44
124
|
export type AppInfo = {
|
|
125
|
+
/** Application ID (e.g., "com.example.myapp") */
|
|
45
126
|
appId: string;
|
|
127
|
+
/** Process ID */
|
|
46
128
|
pid: number;
|
|
129
|
+
/** Open windows */
|
|
47
130
|
windows: Array<{
|
|
48
131
|
id: string;
|
|
49
132
|
title: string | null;
|
|
50
133
|
}>;
|
|
51
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* Options for widget queries.
|
|
137
|
+
*/
|
|
52
138
|
export type QueryOptions = {
|
|
139
|
+
/** Widget name to match */
|
|
53
140
|
name?: string;
|
|
54
|
-
|
|
55
|
-
expanded?: boolean;
|
|
141
|
+
/** Require exact match */
|
|
56
142
|
exact?: boolean;
|
|
143
|
+
/** Query timeout in milliseconds */
|
|
57
144
|
timeout?: number;
|
|
58
145
|
};
|
|
146
|
+
/**
|
|
147
|
+
* Zod schema for app registration parameters.
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
59
150
|
export declare const RegisterParamsSchema: z.ZodObject<{
|
|
60
151
|
appId: z.ZodString;
|
|
61
152
|
pid: z.ZodNumber;
|
|
@@ -66,6 +157,21 @@ export declare const RegisterParamsSchema: z.ZodObject<{
|
|
|
66
157
|
appId: string;
|
|
67
158
|
pid: number;
|
|
68
159
|
}>;
|
|
160
|
+
/**
|
|
161
|
+
* Available IPC methods.
|
|
162
|
+
*/
|
|
69
163
|
export type IpcMethod = "app.register" | "app.unregister" | "widget.getTree" | "widget.query" | "widget.getProps" | "widget.click" | "widget.type" | "widget.fireEvent" | "widget.screenshot";
|
|
164
|
+
/**
|
|
165
|
+
* Union type for any IPC message (request or response).
|
|
166
|
+
*/
|
|
70
167
|
export type IpcMessage = IpcRequest | IpcResponse;
|
|
71
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Gets the XDG runtime directory or falls back to system temp.
|
|
170
|
+
*
|
|
171
|
+
* @returns Path to the runtime directory
|
|
172
|
+
*/
|
|
173
|
+
export declare const getRuntimeDir: () => string;
|
|
174
|
+
/**
|
|
175
|
+
* Default path for the MCP socket file.
|
|
176
|
+
*/
|
|
177
|
+
export declare const DEFAULT_SOCKET_PATH: string;
|
package/dist/protocol/types.js
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for validating IPC requests.
|
|
6
|
+
*/
|
|
2
7
|
export const IpcRequestSchema = z.object({
|
|
3
8
|
id: z.string(),
|
|
4
9
|
method: z.string(),
|
|
5
10
|
params: z.unknown().optional(),
|
|
6
11
|
});
|
|
12
|
+
/**
|
|
13
|
+
* Zod schema for validating IPC errors.
|
|
14
|
+
*/
|
|
15
|
+
export const IpcErrorSchema = z.object({
|
|
16
|
+
code: z.number(),
|
|
17
|
+
message: z.string(),
|
|
18
|
+
data: z.unknown().optional(),
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Zod schema for validating IPC responses.
|
|
22
|
+
*/
|
|
23
|
+
export const IpcResponseSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
result: z.unknown().optional(),
|
|
26
|
+
error: IpcErrorSchema.optional(),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Zod schema for app registration parameters.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
7
32
|
export const RegisterParamsSchema = z.object({
|
|
8
33
|
appId: z.string(),
|
|
9
34
|
pid: z.number(),
|
|
10
35
|
});
|
|
11
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Gets the XDG runtime directory or falls back to system temp.
|
|
38
|
+
*
|
|
39
|
+
* @returns Path to the runtime directory
|
|
40
|
+
*/
|
|
41
|
+
export const getRuntimeDir = () => process.env.XDG_RUNTIME_DIR ?? tmpdir();
|
|
42
|
+
/**
|
|
43
|
+
* Default path for the MCP socket file.
|
|
44
|
+
*/
|
|
45
|
+
export const DEFAULT_SOCKET_PATH = join(getRuntimeDir(), "gtkx-mcp.sock");
|
package/dist/socket-server.d.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import * as net from "node:net";
|
|
3
|
-
import { type IpcMessage, type IpcRequest } from "./protocol/types.js";
|
|
3
|
+
import { type IpcMessage, type IpcRequest, type IpcResponse } from "./protocol/types.js";
|
|
4
4
|
type SocketServerEventMap = {
|
|
5
5
|
connection: [AppConnection];
|
|
6
6
|
disconnection: [AppConnection];
|
|
7
7
|
request: [AppConnection, IpcRequest];
|
|
8
|
+
response: [AppConnection, IpcResponse];
|
|
8
9
|
error: [Error];
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Represents a connected application.
|
|
13
|
+
*/
|
|
10
14
|
export interface AppConnection {
|
|
15
|
+
/** Unique connection identifier */
|
|
11
16
|
id: string;
|
|
17
|
+
/** The underlying socket */
|
|
12
18
|
socket: net.Socket;
|
|
19
|
+
/** Buffer for incomplete messages */
|
|
13
20
|
buffer: string;
|
|
14
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Unix domain socket server for MCP communication.
|
|
24
|
+
*
|
|
25
|
+
* Manages connections from GTKX applications and handles IPC messaging.
|
|
26
|
+
*/
|
|
15
27
|
export declare class SocketServer extends EventEmitter<SocketServerEventMap> {
|
|
16
28
|
private server;
|
|
17
29
|
private connections;
|
package/dist/socket-server.js
CHANGED
|
@@ -2,7 +2,12 @@ import EventEmitter from "node:events";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as net from "node:net";
|
|
4
4
|
import { invalidRequestError } from "./protocol/errors.js";
|
|
5
|
-
import { DEFAULT_SOCKET_PATH, IpcRequestSchema, } from "./protocol/types.js";
|
|
5
|
+
import { DEFAULT_SOCKET_PATH, IpcRequestSchema, IpcResponseSchema, } from "./protocol/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Unix domain socket server for MCP communication.
|
|
8
|
+
*
|
|
9
|
+
* Manages connections from GTKX applications and handles IPC messaging.
|
|
10
|
+
*/
|
|
6
11
|
export class SocketServer extends EventEmitter {
|
|
7
12
|
server = null;
|
|
8
13
|
connections = new Map();
|
|
@@ -61,7 +66,7 @@ export class SocketServer extends EventEmitter {
|
|
|
61
66
|
}
|
|
62
67
|
send(connectionId, message) {
|
|
63
68
|
const connection = this.connections.get(connectionId);
|
|
64
|
-
if (!connection) {
|
|
69
|
+
if (!connection || !connection.socket.writable) {
|
|
65
70
|
return false;
|
|
66
71
|
}
|
|
67
72
|
const data = `${JSON.stringify(message)}\n`;
|
|
@@ -111,15 +116,26 @@ export class SocketServer extends EventEmitter {
|
|
|
111
116
|
this.send(connection.id, response);
|
|
112
117
|
return;
|
|
113
118
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const message = parsed;
|
|
120
|
+
const hasMethod = typeof message.method === "string";
|
|
121
|
+
if (hasMethod) {
|
|
122
|
+
const requestResult = IpcRequestSchema.safeParse(parsed);
|
|
123
|
+
if (requestResult.success) {
|
|
124
|
+
this.emit("request", connection, requestResult.data);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const responseResult = IpcResponseSchema.safeParse(parsed);
|
|
130
|
+
if (responseResult.success) {
|
|
131
|
+
this.emit("response", connection, responseResult.data);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
122
134
|
}
|
|
123
|
-
|
|
135
|
+
const response = {
|
|
136
|
+
id: message.id ?? "unknown",
|
|
137
|
+
error: invalidRequestError("Invalid message format").toIpcError(),
|
|
138
|
+
};
|
|
139
|
+
this.send(connection.id, response);
|
|
124
140
|
}
|
|
125
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "MCP server for AI-powered interaction with GTKX applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -43,9 +43,8 @@
|
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
44
44
|
"zod": "^3.24.0"
|
|
45
45
|
},
|
|
46
|
-
"devDependencies": {},
|
|
47
46
|
"scripts": {
|
|
48
|
-
"build": "tsc -b",
|
|
47
|
+
"build": "tsc -b && cp ../../README.md .",
|
|
49
48
|
"test": "vitest run"
|
|
50
49
|
}
|
|
51
50
|
}
|