@gtkx/mcp 0.21.0 → 1.0.0-rc.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.
Files changed (73) hide show
  1. package/README.md +137 -34
  2. package/bin/gtkx-mcp.js +9 -1
  3. package/dist/app-router.d.ts +35 -0
  4. package/dist/app-router.d.ts.map +1 -0
  5. package/dist/app-router.js +143 -0
  6. package/dist/app-router.js.map +1 -0
  7. package/dist/connection-registry.d.ts +12 -0
  8. package/dist/connection-registry.d.ts.map +1 -0
  9. package/dist/connection-registry.js +39 -0
  10. package/dist/connection-registry.js.map +1 -0
  11. package/dist/internal.d.ts +4 -0
  12. package/dist/internal.d.ts.map +1 -0
  13. package/dist/internal.js +4 -0
  14. package/dist/internal.js.map +1 -0
  15. package/dist/protocol/errors.d.ts +25 -87
  16. package/dist/protocol/errors.d.ts.map +1 -1
  17. package/dist/protocol/errors.js +38 -101
  18. package/dist/protocol/errors.js.map +1 -1
  19. package/dist/protocol/schemas.d.ts +93 -0
  20. package/dist/protocol/schemas.d.ts.map +1 -0
  21. package/dist/protocol/schemas.js +63 -0
  22. package/dist/protocol/schemas.js.map +1 -0
  23. package/dist/reference.d.ts +13 -0
  24. package/dist/reference.d.ts.map +1 -0
  25. package/dist/reference.js +293 -0
  26. package/dist/reference.js.map +1 -0
  27. package/dist/server.d.ts +14 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +244 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/socket-server.d.ts +5 -38
  32. package/dist/socket-server.d.ts.map +1 -1
  33. package/dist/socket-server.js +36 -108
  34. package/dist/socket-server.js.map +1 -1
  35. package/dist/tool.d.ts +22 -0
  36. package/dist/tool.d.ts.map +1 -0
  37. package/dist/tool.js +40 -0
  38. package/dist/tool.js.map +1 -0
  39. package/dist/transport.d.ts +49 -0
  40. package/dist/transport.d.ts.map +1 -0
  41. package/dist/transport.js +152 -0
  42. package/dist/transport.js.map +1 -0
  43. package/package.json +15 -11
  44. package/src/app-router.ts +209 -0
  45. package/src/connection-registry.ts +57 -0
  46. package/src/internal.ts +17 -0
  47. package/src/protocol/errors.ts +67 -110
  48. package/src/protocol/schemas.ts +173 -0
  49. package/src/reference.ts +449 -0
  50. package/src/server.ts +334 -0
  51. package/src/socket-server.ts +46 -147
  52. package/src/tool.ts +73 -0
  53. package/src/transport.ts +242 -0
  54. package/dist/cli.d.ts +0 -3
  55. package/dist/cli.d.ts.map +0 -1
  56. package/dist/cli.js +0 -399
  57. package/dist/cli.js.map +0 -1
  58. package/dist/connection-manager.d.ts +0 -48
  59. package/dist/connection-manager.d.ts.map +0 -1
  60. package/dist/connection-manager.js +0 -185
  61. package/dist/connection-manager.js.map +0 -1
  62. package/dist/index.d.ts +0 -5
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js +0 -5
  65. package/dist/index.js.map +0 -1
  66. package/dist/protocol/types.d.ts +0 -132
  67. package/dist/protocol/types.d.ts.map +0 -1
  68. package/dist/protocol/types.js +0 -46
  69. package/dist/protocol/types.js.map +0 -1
  70. package/src/cli.ts +0 -449
  71. package/src/connection-manager.ts +0 -247
  72. package/src/index.ts +0 -27
  73. package/src/protocol/types.ts +0 -153
package/README.md CHANGED
@@ -1,70 +1,173 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/logo.svg" alt="GTKX" width="100" height="100">
2
+ <img src="https://raw.githubusercontent.com/gtkx-org/gtkx/main/logo.svg" alt="GTKX" width="100" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">GTKX</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Linux application development for the modern age powered by GTK4 and React</strong>
8
+ The React framework for Linux.<br />
9
+ Build GTK4 and Adwaita apps in TypeScript, with React components and hooks driving GNOME's own widgets. What you ship is a GNOME app.
9
10
  </p>
10
11
 
11
12
  <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/gtkx-org/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/gtkx-org/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/gtkx-org/gtkx/discussions"><img src="https://img.shields.io/badge/discussions-GitHub-blue" alt="GitHub Discussions"></a>
13
+ <a href="https://www.npmjs.com/package/create-gtkx"><img src="https://img.shields.io/npm/v/create-gtkx?color=cb3837&logo=npm&label=create-gtkx" alt="npm version" /></a>
14
+ <a href="https://www.npmjs.com/package/create-gtkx"><img src="https://img.shields.io/npm/dm/create-gtkx?color=cb3837&logo=npm&label=downloads" alt="npm downloads" /></a>
15
+ <img src="https://img.shields.io/badge/node-%E2%89%A524-339933?logo=node.js&logoColor=white" alt="Node >= 24" />
16
+ <a href="https://github.com/gtkx-org/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License: MPL-2.0" /></a>
17
+ <a href="https://github.com/gtkx-org/gtkx/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/gtkx-org/gtkx/ci.yml?branch=main&logo=github&label=CI" alt="CI status" /></a>
18
+ <img src="https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white" alt="TypeScript" />
19
+ </p>
20
+
21
+ <p align="center">
22
+ <a href="https://gtkx.dev">Homepage</a> &middot;
23
+ <a href="https://gtkx.dev/guide/why-gtkx">Documentation</a> &middot;
24
+ <a href="https://github.com/gtkx-org/gtkx/tree/main/examples">Examples</a> &middot;
25
+ <a href="https://github.com/gtkx-org/gtkx/blob/main/CONTRIBUTING.md">Contributing</a>
16
26
  </p>
17
27
 
18
28
  ---
19
29
 
30
+ GTKX generates fully typed bindings for the entire GTK4 and Adwaita surface directly from GObject-Introspection. On top of those bindings you get the React programming model: components and hooks driving GObject instances, with Fast Refresh while you develop.
31
+
20
32
  <p align="center">
21
- <img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/demo.gif" alt="GTKX Demo" width="100%">
33
+ <img src="https://raw.githubusercontent.com/gtkx-org/gtkx/main/examples/tutorial/assets/screenshot.png" alt="The Tasks app: an Adwaita window with a sidebar of smart views and colored lists on the left, and a boxed task list on the right." />
22
34
  </p>
23
35
 
24
- ---
36
+ <p align="center">
37
+ <em>The Tasks app you build in the <a href="https://gtkx.dev/tutorial/">tutorial</a>.</em>
38
+ </p>
39
+
40
+ ## Demo
41
+
42
+ The intrinsic elements in this snippet render GTK4 widgets, and ordinary React hooks and events drive them:
43
+
44
+ ```tsx
45
+ import * as Gtk from "@gtkx/gi/gtk";
46
+ import { GtkApplication, GtkApplicationWindow, GtkBox, GtkButton, GtkLabel } from "@gtkx/jsx/gtk";
47
+ import { createRoot, quit } from "@gtkx/react";
48
+ import { useState } from "react";
49
+
50
+ const Counter = () => {
51
+ const [count, setCount] = useState(0);
52
+
53
+ return (
54
+ <GtkApplicationWindow
55
+ title="Hello GTKX"
56
+ defaultWidth={400}
57
+ defaultHeight={300}
58
+ onCloseRequest={quit}
59
+ >
60
+ <GtkBox
61
+ orientation={Gtk.Orientation.VERTICAL}
62
+ spacing={20}
63
+ marginTop={40}
64
+ marginBottom={40}
65
+ marginStart={40}
66
+ marginEnd={40}
67
+ valign={Gtk.Align.CENTER}
68
+ halign={Gtk.Align.CENTER}
69
+ >
70
+ <GtkLabel cssClasses={["title-1"]}>Welcome to GTKX!</GtkLabel>
71
+ <GtkLabel cssClasses={["title-2"]}>{`Count: ${count}`}</GtkLabel>
72
+ <GtkButton
73
+ label="Increment"
74
+ onClicked={() => setCount((c) => c + 1)}
75
+ cssClasses={["suggested-action", "pill"]}
76
+ />
77
+ </GtkBox>
78
+ </GtkApplicationWindow>
79
+ );
80
+ };
81
+
82
+ const App = () => (
83
+ <GtkApplication>
84
+ <Counter />
85
+ </GtkApplication>
86
+ );
87
+
88
+ createRoot().render(<App />);
89
+ ```
90
+
91
+ This is the [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world) example, with `app.tsx` and `index.tsx` combined into a single snippet. `@gtkx/gi` and `@gtkx/jsx` are per-project bindings generated by the CLI, not packages you install from npm.
92
+
93
+ ## Why GTKX
94
+
95
+ ### A declarative layer for the GNOME stack
96
+
97
+ GTK4 is mature, and GtkBuilder XML can lay out a static interface, but nothing re-renders that interface when your application state changes, and nothing hot-reloads it as you work. GTKX adds that missing layer, and the tooling around it, on top of the stack you already know:
98
+
99
+ - a React reconciler that exposes every GObject as a JSX element,
100
+ - a CLI for scaffolding, development, and production builds,
101
+ - a dev server with Fast Refresh that patches your running UI in place,
102
+ - CSS-in-JS styling and high-level list, grid, and dialog components,
103
+ - a Testing Library-style API for querying and driving your widgets in tests,
104
+ - and a Model Context Protocol (MCP) server that exposes your live app to AI agents.
105
+
106
+ ### The full GNOME API surface
107
+
108
+ React Native and similar frameworks hide the native toolkit so one API can run everywhere. GTKX exposes it: GTK4, Adwaita, and any other GObject-Introspection library on your system. Linux-only by design.
109
+
110
+ ### Why Node.js, and why generated bindings
111
+
112
+ GTKX runs on Node.js, which puts native modules, the npm ecosystem, and the tooling built for Node.js APIs within reach. GJS is GNOME's own runtime, built on SpiderMonkey rather than V8; node-gtk runs on Node.js but is lightly maintained, on the older nan/V8 ABI rather than N-API, and still centered on GTK3. The [why-gtkx guide](https://gtkx.dev/guide/why-gtkx) covers the comparison in full.
25
113
 
26
- GTKX is a modern framework for building native Linux applications using React and GTK. It provides the full range of GTK4, GLib, and Node.js APIs, allowing you to create rich, performant desktop applications with the tools and libraries you already know.
114
+ GTKX generates the TypeScript types and the native FFI calls from the same GObject-Introspection data, so the types cannot drift from the calls they back. Codegen covers the whole GTK4 and Adwaita surface.
27
115
 
28
- ## Features
116
+ At runtime, the native Rust core calls straight into the system GTK4, Adwaita, and GLib libraries through libffi, without loading libgirepository at all.
29
117
 
30
- - **React 19** — Hooks, concurrent features, and the component model you know
31
- - **Fully native Node.js environment** - Runs on vanilla Node.js, with the help of a Neon native module
32
- - **TypeScript first** — Full type safety with auto-generated bindings
33
- - **Rich GLib support** — Provides bindings for most modern GLib/GObject libraries, including Adwaita
34
- - **HMR** — Fast refresh during development powered by Vite
35
- - **CSS-in-JS styling** — Easy styling with GTK CSS powered by Emotion
36
- - **Testing library** — Testing Library-inspired API for testing components and E2E
118
+ ## Quick start
37
119
 
38
- ## Quick Start
120
+ GTKX is Linux-only and needs Node.js 24 or later. See [Requirements](#requirements).
39
121
 
40
- ```bash
41
- npx @gtkx/cli create my-app
122
+ Scaffold a new app with the `create-gtkx` initializer:
123
+
124
+ ```sh
125
+ npm create gtkx@rc
126
+ ```
127
+
128
+ The same command works with other package managers: `pnpm create gtkx@rc` or `yarn create gtkx@rc`.
129
+
130
+ Then run your new app:
131
+
132
+ ```sh
42
133
  cd my-app
43
134
  npm run dev
44
135
  ```
45
136
 
46
- ## Examples
137
+ To go further, follow the [tutorial](https://gtkx.dev/tutorial/).
47
138
 
48
- Explore complete applications in the [`examples/`](./examples) directory:
139
+ ## Documentation
49
140
 
50
- - **[hello-world](./examples/hello-world)** Minimal application showing a counter
51
- - **[gtk-demo](./examples/gtk-demo)** — Full replica of the official GTK demo app
52
- - **[tutorial](./examples/tutorial)** — Notes app from the tutorial with GSettings and Adwaita
53
- - **[browser](./examples/browser)** — Simple browser using WebKitWebView
141
+ The documentation at **[gtkx.dev](https://gtkx.dev)** includes a step-by-step tutorial that builds a complete GNOME app, from scaffolding to packaging and shipping, plus guides and a full API reference.
54
142
 
55
- ## Documentation
143
+ **[Read the docs &rarr;](https://gtkx.dev/guide/why-gtkx)**
56
144
 
57
- Visit [https://gtkx.dev](https://gtkx.dev) for the full documentation.
145
+ ## Requirements
58
146
 
59
- ## Contributing
147
+ GTKX is Linux-only. You need:
60
148
 
61
- Contributions are welcome! Please see the [contributing guidelines](./CONTRIBUTING.md).
149
+ - Linux with the GTK4 (4.20 or later), Adwaita (1.8 or later), and GLib development libraries
150
+ - Node.js 24 or later
62
151
 
63
- ## Community
152
+ The `@gtkx/native` addon ships prebuilt for x64 and arm64 glibc Linux; other targets need to build it from the GTKX repository, which requires a Rust toolchain.
153
+
154
+ ## Examples
155
+
156
+ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
157
+
158
+ - [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the counter above.
159
+ - [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo): a React port of the official GTK4 widget showcase, covering lists, dialogs, gestures, CSS, and OpenGL.
160
+ - [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser): a WebKitWebView-based web browser.
161
+ - [`tutorial`](https://github.com/gtkx-org/gtkx/tree/main/examples/tutorial): the Tasks app the documentation builds.
162
+
163
+ ## Status
164
+
165
+ GTKX 1.0 is at the release candidate stage.
166
+
167
+ ## Contributing
64
168
 
65
- - [GitHub Discussions](https://github.com/gtkx-org/gtkx/discussions) Questions, ideas, and general discussion
66
- - [Issue Tracker](https://github.com/gtkx-org/gtkx/issues) — Bug reports and feature requests
169
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/gtkx-org/gtkx/blob/main/CONTRIBUTING.md), the [Code of Conduct](https://github.com/gtkx-org/gtkx/blob/main/CODE_OF_CONDUCT.md), and the [security policy](https://github.com/gtkx-org/gtkx/blob/main/SECURITY.md). Building the repo needs Node.js 24 or later, pnpm, and a Rust toolchain.
67
170
 
68
171
  ## License
69
172
 
70
- [MPL-2.0](./LICENSE)
173
+ GTKX is licensed under [MPL-2.0](https://github.com/gtkx-org/gtkx/blob/main/LICENSE).
package/bin/gtkx-mcp.js CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import "../dist/cli.js";
2
+ import process from "node:process";
3
+ import { log, main } from "../dist/server.js";
4
+
5
+ try {
6
+ await main();
7
+ } catch (error) {
8
+ log.error("fatal error", error);
9
+ process.exit(1);
10
+ }
@@ -0,0 +1,35 @@
1
+ import { type AppInfo } from "./protocol/schemas.js";
2
+ import { type AppConnections, type ProtocolConnection } from "./transport.js";
3
+ type AppRegisteredEvent = CustomEvent<AppInfo>;
4
+ type AppUnregisteredEvent = CustomEvent<string>;
5
+ type RegisteredApp = {
6
+ info: AppInfo;
7
+ connection: ProtocolConnection;
8
+ };
9
+ declare function appRegisteredEvent(info: AppInfo): AppRegisteredEvent;
10
+ declare function appUnregisteredEvent(applicationId: string): AppUnregisteredEvent;
11
+ declare class AppRouter extends EventTarget {
12
+ private static defaultWaitTimeout;
13
+ private apps;
14
+ private connectionToApp;
15
+ private requestTimeout;
16
+ private connections;
17
+ constructor(connections: AppConnections, options?: {
18
+ requestTimeout?: number;
19
+ });
20
+ private resolveTargetApp;
21
+ private handleRequest;
22
+ private handleRegister;
23
+ private handleUnregister;
24
+ private acknowledge;
25
+ private sendError;
26
+ private removeApp;
27
+ getApps(): AppInfo[];
28
+ hasConnectedApps(): boolean;
29
+ getDefaultApp(): RegisteredApp | undefined;
30
+ getProjectRoot(): string | undefined;
31
+ waitForApp(timeout?: number): Promise<AppInfo>;
32
+ sendToApp<T>(applicationId: string | undefined, method: string, params?: unknown): Promise<T>;
33
+ }
34
+ export { AppRouter, appRegisteredEvent, appUnregisteredEvent, type AppRegisteredEvent, type AppUnregisteredEvent };
35
+ //# sourceMappingURL=app-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-router.d.ts","sourceRoot":"","sources":["../src/app-router.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,OAAO,EAAqD,MAAM,uBAAuB,CAAC;AACxG,OAAO,EACH,KAAK,cAAc,EAInB,KAAK,kBAAkB,EAC1B,MAAM,gBAAgB,CAAC;AAExB,KAAK,kBAAkB,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;AAC/C,KAAK,oBAAoB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;AAEhD,KAAK,aAAa,GAAG;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,kBAAkB,CAAC;CAClC,CAAC;AAIF,iBAAS,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAE7D;AAED,iBAAS,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,oBAAoB,CAEzE;AAED,cAAM,SAAU,SAAQ,WAAW;IAC/B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAU;IAE3C,OAAO,CAAC,IAAI,CAAyC;IAErD,OAAO,CAAC,eAAe,CAAkC;IAEzD,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,WAAW,CAAiB;gBAExB,WAAW,EAAE,cAAc,EAAE,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO;IAelF,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,SAAS;IAgBjB,OAAO,IAAI,OAAO,EAAE;IAIpB,gBAAgB,IAAI,OAAO;IAI3B,aAAa,IAAI,aAAa,GAAG,SAAS;IAM1C,cAAc,IAAI,MAAM,GAAG,SAAS;IAIpC,UAAU,CAAC,OAAO,GAAE,MAAqC,GAAG,OAAO,CAAC,OAAO,CAAC;IA6BtE,SAAS,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;CActG;AAED,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { appNotFoundError, connectionWriteFailedError, invalidRequestError, methodNotFoundError, noAppConnectedError, } from "./protocol/errors.js";
2
+ import { RegisterParamsSchema } from "./protocol/schemas.js";
3
+ import { ConnectionClosedError, } from "./transport.js";
4
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
5
+ function appRegisteredEvent(info) {
6
+ return new CustomEvent("appRegistered", { detail: info });
7
+ }
8
+ function appUnregisteredEvent(applicationId) {
9
+ return new CustomEvent("appUnregistered", { detail: applicationId });
10
+ }
11
+ class AppRouter extends EventTarget {
12
+ static defaultWaitTimeout = 10_000;
13
+ apps = new Map();
14
+ connectionToApp = new Map();
15
+ requestTimeout;
16
+ connections;
17
+ constructor(connections, options = {}) {
18
+ super();
19
+ this.connections = connections;
20
+ this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
21
+ this.connections.addEventListener("request", (event) => {
22
+ const { connection, request } = event.detail;
23
+ this.handleRequest(connection, request);
24
+ });
25
+ this.connections.addEventListener("disconnection", (event) => {
26
+ this.removeApp(event.detail);
27
+ });
28
+ }
29
+ resolveTargetApp(applicationId) {
30
+ const app = applicationId ? this.apps.get(applicationId) : this.getDefaultApp();
31
+ if (app) {
32
+ return app;
33
+ }
34
+ if (applicationId) {
35
+ throw appNotFoundError(applicationId);
36
+ }
37
+ throw noAppConnectedError();
38
+ }
39
+ handleRequest(connection, request) {
40
+ if (request.method === "app.register") {
41
+ this.handleRegister(connection, request);
42
+ }
43
+ else if (request.method === "app.unregister") {
44
+ this.handleUnregister(connection, request);
45
+ }
46
+ else {
47
+ this.sendError(connection, request, methodNotFoundError(request.method));
48
+ }
49
+ }
50
+ handleRegister(connection, request) {
51
+ const parseResult = RegisterParamsSchema.safeParse(request.params);
52
+ if (!parseResult.success) {
53
+ this.sendError(connection, request, invalidRequestError(parseResult.error.message));
54
+ return;
55
+ }
56
+ const params = parseResult.data;
57
+ const appInfo = {
58
+ applicationId: params.applicationId,
59
+ pid: params.pid,
60
+ ...(params.projectRoot !== undefined && { projectRoot: params.projectRoot }),
61
+ };
62
+ this.apps.set(params.applicationId, { info: appInfo, connection });
63
+ this.connectionToApp.set(connection.id, params.applicationId);
64
+ this.acknowledge(connection, request);
65
+ this.dispatchEvent(appRegisteredEvent(appInfo));
66
+ }
67
+ handleUnregister(connection, request) {
68
+ this.removeApp(connection);
69
+ this.acknowledge(connection, request);
70
+ }
71
+ acknowledge(connection, request) {
72
+ const response = {
73
+ id: request.id,
74
+ result: { success: true },
75
+ };
76
+ this.connections.send(connection.id, response);
77
+ }
78
+ sendError(connection, request, error) {
79
+ this.connections.send(connection.id, {
80
+ id: request.id,
81
+ error: error.toErrorObject(),
82
+ });
83
+ }
84
+ removeApp(connection) {
85
+ const applicationId = this.connectionToApp.get(connection.id);
86
+ this.connectionToApp.delete(connection.id);
87
+ if (applicationId === undefined) {
88
+ return;
89
+ }
90
+ if (this.apps.get(applicationId)?.connection !== connection) {
91
+ return;
92
+ }
93
+ this.apps.delete(applicationId);
94
+ this.dispatchEvent(appUnregisteredEvent(applicationId));
95
+ }
96
+ getApps() {
97
+ return this.apps.values().map((app) => app.info).toArray();
98
+ }
99
+ hasConnectedApps() {
100
+ return this.apps.size > 0;
101
+ }
102
+ getDefaultApp() {
103
+ const first = this.apps.values().next();
104
+ return first.done ? undefined : first.value;
105
+ }
106
+ getProjectRoot() {
107
+ return this.getDefaultApp()?.info.projectRoot;
108
+ }
109
+ waitForApp(timeout = AppRouter.defaultWaitTimeout) {
110
+ const defaultApp = this.getDefaultApp();
111
+ if (defaultApp) {
112
+ return Promise.resolve(defaultApp.info);
113
+ }
114
+ return new Promise((resolve, reject) => {
115
+ const timeoutId = setTimeout(() => {
116
+ this.removeEventListener("appRegistered", onRegister);
117
+ reject(new Error(`Timeout waiting for app registration after ${String(timeout)}ms. ` +
118
+ "Make sure your GTKX app is running with 'gtkx dev'."));
119
+ }, timeout);
120
+ const onRegister = (event) => {
121
+ clearTimeout(timeoutId);
122
+ this.removeEventListener("appRegistered", onRegister);
123
+ resolve(event.detail);
124
+ };
125
+ this.addEventListener("appRegistered", onRegister);
126
+ });
127
+ }
128
+ async sendToApp(applicationId, method, params) {
129
+ const app = this.resolveTargetApp(applicationId);
130
+ try {
131
+ return await app.connection.send(method, params, this.requestTimeout);
132
+ }
133
+ catch (error) {
134
+ if (error instanceof ConnectionClosedError) {
135
+ this.removeApp(app.connection);
136
+ throw connectionWriteFailedError(app.info.applicationId);
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+ }
142
+ export { AppRouter, appRegisteredEvent, appUnregisteredEvent };
143
+ //# sourceMappingURL=app-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-router.js","sourceRoot":"","sources":["../src/app-router.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n appNotFoundError,\n connectionWriteFailedError,\n invalidRequestError,\n methodNotFoundError,\n noAppConnectedError,\n type ProtocolError,\n} from \"./protocol/errors.js\";\nimport { type AppInfo, RegisterParamsSchema, type Request, type Response } from \"./protocol/schemas.js\";\nimport {\n type AppConnections,\n ConnectionClosedError,\n type ConnectionEvent,\n type ConnectionRequestEvent,\n type ProtocolConnection,\n} from \"./transport.js\";\n\ntype AppRegisteredEvent = CustomEvent<AppInfo>;\ntype AppUnregisteredEvent = CustomEvent<string>;\n\ntype RegisteredApp = {\n info: AppInfo;\n connection: ProtocolConnection;\n};\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\nfunction appRegisteredEvent(info: AppInfo): AppRegisteredEvent {\n return new CustomEvent(\"appRegistered\", { detail: info });\n}\n\nfunction appUnregisteredEvent(applicationId: string): AppUnregisteredEvent {\n return new CustomEvent(\"appUnregistered\", { detail: applicationId });\n}\n\nclass AppRouter extends EventTarget {\n private static defaultWaitTimeout = 10_000;\n\n private apps: Map<string, RegisteredApp> = new Map();\n\n private connectionToApp: Map<string, string> = new Map();\n\n private requestTimeout: number;\n\n private connections: AppConnections;\n\n constructor(connections: AppConnections, options: { requestTimeout?: number } = {}) {\n super();\n this.connections = connections;\n this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n this.connections.addEventListener(\"request\", (event) => {\n const { connection, request } = (event as ConnectionRequestEvent).detail;\n this.handleRequest(connection, request);\n });\n\n this.connections.addEventListener(\"disconnection\", (event) => {\n this.removeApp((event as ConnectionEvent).detail);\n });\n }\n\n private resolveTargetApp(applicationId: string | undefined): RegisteredApp {\n const app = applicationId ? this.apps.get(applicationId) : this.getDefaultApp();\n\n if (app) {\n return app;\n }\n\n if (applicationId) {\n throw appNotFoundError(applicationId);\n }\n\n throw noAppConnectedError();\n }\n\n private handleRequest(connection: ProtocolConnection, request: Request): void {\n if (request.method === \"app.register\") {\n this.handleRegister(connection, request);\n } else if (request.method === \"app.unregister\") {\n this.handleUnregister(connection, request);\n } else {\n this.sendError(connection, request, methodNotFoundError(request.method));\n }\n }\n\n private handleRegister(connection: ProtocolConnection, request: Request): void {\n const parseResult = RegisterParamsSchema.safeParse(request.params);\n\n if (!parseResult.success) {\n this.sendError(connection, request, invalidRequestError(parseResult.error.message));\n\n return;\n }\n\n const params = parseResult.data;\n\n const appInfo: AppInfo = {\n applicationId: params.applicationId,\n pid: params.pid,\n ...(params.projectRoot !== undefined && { projectRoot: params.projectRoot }),\n };\n\n this.apps.set(params.applicationId, { info: appInfo, connection });\n this.connectionToApp.set(connection.id, params.applicationId);\n this.acknowledge(connection, request);\n this.dispatchEvent(appRegisteredEvent(appInfo));\n }\n\n private handleUnregister(connection: ProtocolConnection, request: Request): void {\n this.removeApp(connection);\n this.acknowledge(connection, request);\n }\n\n private acknowledge(connection: ProtocolConnection, request: Request): void {\n const response: Response = {\n id: request.id,\n result: { success: true },\n };\n\n this.connections.send(connection.id, response);\n }\n\n private sendError(connection: ProtocolConnection, request: Request, error: ProtocolError): void {\n this.connections.send(connection.id, {\n id: request.id,\n error: error.toErrorObject(),\n });\n }\n\n private removeApp(connection: ProtocolConnection): void {\n const applicationId = this.connectionToApp.get(connection.id);\n this.connectionToApp.delete(connection.id);\n\n if (applicationId === undefined) {\n return;\n }\n\n if (this.apps.get(applicationId)?.connection !== connection) {\n return;\n }\n\n this.apps.delete(applicationId);\n this.dispatchEvent(appUnregisteredEvent(applicationId));\n }\n\n getApps(): AppInfo[] {\n return this.apps.values().map((app) => app.info).toArray();\n }\n\n hasConnectedApps(): boolean {\n return this.apps.size > 0;\n }\n\n getDefaultApp(): RegisteredApp | undefined {\n const first = this.apps.values().next();\n\n return first.done ? undefined : first.value;\n }\n\n getProjectRoot(): string | undefined {\n return this.getDefaultApp()?.info.projectRoot;\n }\n\n waitForApp(timeout: number = AppRouter.defaultWaitTimeout): Promise<AppInfo> {\n const defaultApp = this.getDefaultApp();\n\n if (defaultApp) {\n return Promise.resolve(defaultApp.info);\n }\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.removeEventListener(\"appRegistered\", onRegister);\n\n reject(\n new Error(\n `Timeout waiting for app registration after ${String(timeout)}ms. ` +\n \"Make sure your GTKX app is running with 'gtkx dev'.\",\n ),\n );\n }, timeout);\n\n const onRegister = (event: Event): void => {\n clearTimeout(timeoutId);\n this.removeEventListener(\"appRegistered\", onRegister);\n resolve((event as AppRegisteredEvent).detail);\n };\n\n this.addEventListener(\"appRegistered\", onRegister);\n });\n }\n\n async sendToApp<T>(applicationId: string | undefined, method: string, params?: unknown): Promise<T> {\n const app = this.resolveTargetApp(applicationId);\n\n try {\n return await app.connection.send<T>(method, params, this.requestTimeout);\n } catch (error) {\n if (error instanceof ConnectionClosedError) {\n this.removeApp(app.connection);\n throw connectionWriteFailedError(app.info.applicationId);\n }\n\n throw error;\n }\n }\n}\n\nexport { AppRouter, appRegisteredEvent, appUnregisteredEvent, type AppRegisteredEvent, type AppUnregisteredEvent };\n"]}
@@ -0,0 +1,12 @@
1
+ import type { Socket } from "node:net";
2
+ import type { Message } from "./protocol/schemas.js";
3
+ import { type AppConnections, ProtocolConnection } from "./transport.js";
4
+ declare class ConnectionRegistry extends EventTarget implements AppConnections {
5
+ private connections;
6
+ private sockets;
7
+ register(socket: Socket): ProtocolConnection;
8
+ send(connectionId: string, message: Message): void;
9
+ dispose(reason: string): void;
10
+ }
11
+ export { ConnectionRegistry };
12
+ //# sourceMappingURL=connection-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-registry.d.ts","sourceRoot":"","sources":["../src/connection-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EACH,KAAK,cAAc,EAInB,kBAAkB,EACrB,MAAM,gBAAgB,CAAC;AAExB,cAAM,kBAAmB,SAAQ,WAAY,YAAW,cAAc;IAClE,OAAO,CAAC,WAAW,CAA8C;IACjE,OAAO,CAAC,OAAO,CAAkC;IAEjD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB;IAqB5C,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAUlD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAShC;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { connectionDisconnectionEvent, connectionErrorEvent, connectionRequestEvent, ProtocolConnection, } from "./transport.js";
2
+ class ConnectionRegistry extends EventTarget {
3
+ connections = new Map();
4
+ sockets = new Map();
5
+ register(socket) {
6
+ const connection = ProtocolConnection.fromSocket(socket, {
7
+ onClose: () => {
8
+ this.connections.delete(connection.id);
9
+ this.sockets.delete(connection.id);
10
+ this.dispatchEvent(connectionDisconnectionEvent(connection));
11
+ },
12
+ onError: (error) => this.dispatchEvent(connectionErrorEvent(error)),
13
+ });
14
+ this.connections.set(connection.id, connection);
15
+ this.sockets.set(connection.id, socket);
16
+ connection.on("request", (request) => this.dispatchEvent(connectionRequestEvent(connection, request)));
17
+ connection.on("invalid", ({ id: badId, error }) => {
18
+ connection.write({ id: badId, error: error.toErrorObject() });
19
+ });
20
+ return connection;
21
+ }
22
+ send(connectionId, message) {
23
+ const connection = this.connections.get(connectionId);
24
+ if (!connection) {
25
+ return;
26
+ }
27
+ connection.write(message);
28
+ }
29
+ dispose(reason) {
30
+ for (const connection of this.connections.values()) {
31
+ connection.rejectPending(new Error(reason));
32
+ }
33
+ for (const socket of this.sockets.values()) {
34
+ socket.destroy();
35
+ }
36
+ }
37
+ }
38
+ export { ConnectionRegistry };
39
+ //# sourceMappingURL=connection-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-registry.js","sourceRoot":"","sources":["../src/connection-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,4BAA4B,EAC5B,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,GACrB,MAAM,gBAAgB,CAAC;AAExB,MAAM,kBAAmB,SAAQ,WAAW;IAChC,WAAW,GAAoC,IAAI,GAAG,EAAE,CAAC;IACzD,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IAEjD,QAAQ,CAAC,MAAc;QACnB,MAAM,UAAU,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,EAAE;YACrD,OAAO,EAAE,GAAG,EAAE;gBACV,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACnC,IAAI,CAAC,aAAa,CAAC,4BAA4B,CAAC,UAAU,CAAC,CAAC,CAAC;YACjE,CAAC;YACD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;SACtE,CAAC,CAAC;QAEH,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAEvG,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;YAC9C,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,IAAI,CAAC,YAAoB,EAAE,OAAgB;QACvC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAEtD,IAAI,CAAC,UAAU,EAAE,CAAC;YACd,OAAO;QACX,CAAC;QAED,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,CAAC,MAAc;QAClB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YACjD,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAChD,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;IACL,CAAC;CACJ;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import type { Socket } from \"node:net\";\nimport type { Message } from \"./protocol/schemas.js\";\nimport {\n type AppConnections,\n connectionDisconnectionEvent,\n connectionErrorEvent,\n connectionRequestEvent,\n ProtocolConnection,\n} from \"./transport.js\";\n\nclass ConnectionRegistry extends EventTarget implements AppConnections {\n private connections: Map<string, ProtocolConnection> = new Map();\n private sockets: Map<string, Socket> = new Map();\n\n register(socket: Socket): ProtocolConnection {\n const connection = ProtocolConnection.fromSocket(socket, {\n onClose: () => {\n this.connections.delete(connection.id);\n this.sockets.delete(connection.id);\n this.dispatchEvent(connectionDisconnectionEvent(connection));\n },\n onError: (error) => this.dispatchEvent(connectionErrorEvent(error)),\n });\n\n this.connections.set(connection.id, connection);\n this.sockets.set(connection.id, socket);\n connection.on(\"request\", (request) => this.dispatchEvent(connectionRequestEvent(connection, request)));\n\n connection.on(\"invalid\", ({ id: badId, error }) => {\n connection.write({ id: badId, error: error.toErrorObject() });\n });\n\n return connection;\n }\n\n send(connectionId: string, message: Message): void {\n const connection = this.connections.get(connectionId);\n\n if (!connection) {\n return;\n }\n\n connection.write(message);\n }\n\n dispose(reason: string): void {\n for (const connection of this.connections.values()) {\n connection.rejectPending(new Error(reason));\n }\n\n for (const socket of this.sockets.values()) {\n socket.destroy();\n }\n }\n}\n\nexport { ConnectionRegistry };\n"]}
@@ -0,0 +1,4 @@
1
+ export { ErrorCode, invalidRequestError, methodNotFoundError, ProtocolError, widgetNotFoundError, } from "./protocol/errors.js";
2
+ export { DEFAULT_SOCKET_PATH, type ParamsSchema, type Request, type SerializedWidget, type ServerInitiatedMethod, type ServerRequestParams, ServerRequestParamsSchemas, } from "./protocol/schemas.js";
3
+ export { ProtocolConnection } from "./transport.js";
4
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACH,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,0BAA0B,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { ErrorCode, invalidRequestError, methodNotFoundError, ProtocolError, widgetNotFoundError, } from "./protocol/errors.js";
2
+ export { DEFAULT_SOCKET_PATH, ServerRequestParamsSchemas, } from "./protocol/schemas.js";
3
+ export { ProtocolConnection } from "./transport.js";
4
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACH,mBAAmB,EAMnB,0BAA0B,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export {\n ErrorCode,\n invalidRequestError,\n methodNotFoundError,\n ProtocolError,\n widgetNotFoundError,\n} from \"./protocol/errors.js\";\nexport {\n DEFAULT_SOCKET_PATH,\n type ParamsSchema,\n type Request,\n type SerializedWidget,\n type ServerInitiatedMethod,\n type ServerRequestParams,\n ServerRequestParamsSchemas,\n} from \"./protocol/schemas.js\";\nexport { ProtocolConnection } from \"./transport.js\";\n"]}
@@ -1,93 +1,31 @@
1
- /**
2
- * Error codes for MCP protocol errors.
3
- */
4
- export declare 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
- * Error class for MCP protocol errors.
32
- *
33
- * Contains an error code, message, and optional additional data.
34
- */
35
- export declare class McpError extends Error {
36
- /** The MCP error code */
37
- readonly code: McpErrorCode;
38
- /** Additional error context */
39
- readonly data?: unknown;
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
- */
46
- toIpcError(): {
1
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
2
+ declare const ErrorCode: {
3
+ readonly INTERNAL_ERROR: 1000;
4
+ readonly NO_APP_CONNECTED: 1001;
5
+ readonly APP_NOT_FOUND: 1002;
6
+ readonly WIDGET_NOT_FOUND: 1003;
7
+ readonly CONNECTION_WRITE_FAILED: 1004;
8
+ readonly REQUEST_TIMEOUT: 1005;
9
+ readonly INVALID_REQUEST: 1006;
10
+ readonly METHOD_NOT_FOUND: 1007;
11
+ };
12
+ declare function isErrorCode(code: number): code is ErrorCode;
13
+ declare function noAppConnectedError(): ProtocolError;
14
+ declare function appNotFoundError(applicationId: string): ProtocolError;
15
+ declare function connectionWriteFailedError(applicationId: string): ProtocolError;
16
+ declare function widgetNotFoundError(widgetId: string): ProtocolError;
17
+ declare function requestTimeoutError(timeout: number): ProtocolError;
18
+ declare function invalidRequestError(reason: string): ProtocolError;
19
+ declare function methodNotFoundError(method: string): ProtocolError;
20
+ declare class ProtocolError extends Error {
21
+ code: ErrorCode;
22
+ data?: unknown;
23
+ constructor(code: ErrorCode, message: string, data?: unknown);
24
+ toErrorObject(): {
47
25
  code: number;
48
26
  message: string;
49
27
  data?: unknown;
50
28
  };
51
29
  }
52
- /**
53
- * Creates an error for when no GTKX application is connected.
54
- *
55
- * @returns McpError with NO_APP_CONNECTED code
56
- */
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
- */
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
- */
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
- */
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
- */
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
- */
92
- export declare function methodNotFoundError(method: string): McpError;
30
+ export { ErrorCode, isErrorCode, noAppConnectedError, appNotFoundError, connectionWriteFailedError, widgetNotFoundError, requestTimeoutError, invalidRequestError, methodNotFoundError, ProtocolError, };
93
31
  //# sourceMappingURL=errors.d.ts.map