@gtkx/mcp 0.20.0 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -55
- package/bin/gtkx-mcp.js +8 -1
- package/dist/app-router.d.ts +35 -0
- package/dist/app-router.d.ts.map +1 -0
- package/dist/app-router.js +130 -0
- package/dist/app-router.js.map +1 -0
- package/dist/connection-registry.d.ts +12 -0
- package/dist/connection-registry.d.ts.map +1 -0
- package/dist/connection-registry.js +38 -0
- package/dist/connection-registry.js.map +1 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -0
- package/dist/protocol/errors.d.ts +24 -87
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +27 -91
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/schemas.d.ts +89 -0
- package/dist/protocol/schemas.d.ts.map +1 -0
- package/dist/protocol/schemas.js +56 -0
- package/dist/protocol/schemas.js.map +1 -0
- package/dist/reference.d.ts +13 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.js +250 -0
- package/dist/reference.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +220 -0
- package/dist/server.js.map +1 -0
- package/dist/socket-server.d.ts +4 -38
- package/dist/socket-server.d.ts.map +1 -1
- package/dist/socket-server.js +26 -109
- package/dist/socket-server.js.map +1 -1
- package/dist/tool.d.ts +22 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +36 -0
- package/dist/tool.js.map +1 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +121 -0
- package/dist/transport.js.map +1 -0
- package/package.json +17 -11
- package/src/app-router.ts +172 -0
- package/src/connection-registry.ts +42 -0
- package/src/internal.ts +17 -0
- package/src/protocol/errors.ts +44 -100
- package/src/protocol/schemas.ts +148 -0
- package/src/reference.ts +340 -0
- package/src/server.ts +281 -0
- package/src/socket-server.ts +30 -154
- package/src/tool.ts +66 -0
- package/src/transport.ts +165 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -399
- package/dist/cli.js.map +0 -1
- package/dist/connection-manager.d.ts +0 -48
- package/dist/connection-manager.d.ts.map +0 -1
- package/dist/connection-manager.js +0 -185
- package/dist/connection-manager.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +0 -1
- package/dist/protocol/types.d.ts +0 -132
- package/dist/protocol/types.d.ts.map +0 -1
- package/dist/protocol/types.js +0 -46
- package/dist/protocol/types.js.map +0 -1
- package/src/cli.ts +0 -449
- package/src/connection-manager.ts +0 -247
- package/src/index.ts +0 -27
- package/src/protocol/types.ts +0 -153
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import {
|
|
3
|
+
appNotFoundError,
|
|
4
|
+
connectionWriteFailedError,
|
|
5
|
+
invalidRequestError,
|
|
6
|
+
methodNotFoundError,
|
|
7
|
+
noAppConnectedError,
|
|
8
|
+
type ProtocolError,
|
|
9
|
+
} from "./protocol/errors.js";
|
|
10
|
+
import { type AppInfo, RegisterParamsSchema, type Request, type Response } from "./protocol/schemas.js";
|
|
11
|
+
import { type AppConnections, ConnectionClosedError, type ProtocolConnection } from "./transport.js";
|
|
12
|
+
|
|
13
|
+
type AppRouterEventMap = {
|
|
14
|
+
appRegistered: [AppInfo];
|
|
15
|
+
appUnregistered: [string];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type RegisteredApp = {
|
|
19
|
+
info: AppInfo;
|
|
20
|
+
connection: ProtocolConnection;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
24
|
+
|
|
25
|
+
export class AppRouter extends EventEmitter<AppRouterEventMap> {
|
|
26
|
+
private static DEFAULT_WAIT_TIMEOUT = 10000;
|
|
27
|
+
|
|
28
|
+
private apps: Map<string, RegisteredApp> = new Map();
|
|
29
|
+
private connectionToApp: Map<string, string> = new Map();
|
|
30
|
+
private requestTimeout: number;
|
|
31
|
+
private connections: AppConnections;
|
|
32
|
+
|
|
33
|
+
constructor(connections: AppConnections, options: { requestTimeout?: number } = {}) {
|
|
34
|
+
super();
|
|
35
|
+
this.connections = connections;
|
|
36
|
+
this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
37
|
+
|
|
38
|
+
this.connections.on("request", (connection, request) => {
|
|
39
|
+
this.handleRequest(connection, request);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.connections.on("disconnection", (connection) => {
|
|
43
|
+
this.removeApp(connection);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getApps(): AppInfo[] {
|
|
48
|
+
return Array.from(this.apps.values()).map((app) => app.info);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
hasConnectedApps(): boolean {
|
|
52
|
+
return this.apps.size > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getDefaultApp(): RegisteredApp | undefined {
|
|
56
|
+
const first = this.apps.values().next();
|
|
57
|
+
return first.done ? undefined : first.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getProjectRoot(): string | undefined {
|
|
61
|
+
return this.getDefaultApp()?.info.projectRoot;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
waitForApp(timeout: number = AppRouter.DEFAULT_WAIT_TIMEOUT): Promise<AppInfo> {
|
|
65
|
+
const defaultApp = this.getDefaultApp();
|
|
66
|
+
if (defaultApp) {
|
|
67
|
+
return Promise.resolve(defaultApp.info);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timeoutId = setTimeout(() => {
|
|
72
|
+
this.off("appRegistered", onRegister);
|
|
73
|
+
reject(
|
|
74
|
+
new Error(
|
|
75
|
+
`Timeout waiting for app registration after ${timeout}ms. ` +
|
|
76
|
+
"Make sure your GTKX app is running with 'gtkx dev'.",
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
}, timeout);
|
|
80
|
+
|
|
81
|
+
const onRegister = (appInfo: AppInfo) => {
|
|
82
|
+
clearTimeout(timeoutId);
|
|
83
|
+
this.off("appRegistered", onRegister);
|
|
84
|
+
resolve(appInfo);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.on("appRegistered", onRegister);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async sendToApp<T>(applicationId: string | undefined, method: string, params?: unknown): Promise<T> {
|
|
92
|
+
const app = applicationId ? this.apps.get(applicationId) : this.getDefaultApp();
|
|
93
|
+
|
|
94
|
+
if (!app) {
|
|
95
|
+
if (applicationId) {
|
|
96
|
+
throw appNotFoundError(applicationId);
|
|
97
|
+
}
|
|
98
|
+
throw noAppConnectedError();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
return await app.connection.send<T>(method, params, this.requestTimeout);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof ConnectionClosedError) {
|
|
105
|
+
this.removeApp(app.connection);
|
|
106
|
+
throw connectionWriteFailedError(app.info.applicationId);
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private handleRequest(connection: ProtocolConnection, request: Request): void {
|
|
113
|
+
if (request.method === "app.register") {
|
|
114
|
+
this.handleRegister(connection, request);
|
|
115
|
+
} else if (request.method === "app.unregister") {
|
|
116
|
+
this.handleUnregister(connection, request);
|
|
117
|
+
} else {
|
|
118
|
+
this.sendError(connection, request, methodNotFoundError(request.method));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private handleRegister(connection: ProtocolConnection, request: Request): void {
|
|
123
|
+
const parseResult = RegisterParamsSchema.safeParse(request.params);
|
|
124
|
+
if (!parseResult.success) {
|
|
125
|
+
this.sendError(connection, request, invalidRequestError(parseResult.error.message));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const params = parseResult.data;
|
|
130
|
+
const appInfo: AppInfo = {
|
|
131
|
+
applicationId: params.applicationId,
|
|
132
|
+
pid: params.pid,
|
|
133
|
+
...(params.projectRoot === undefined ? {} : { projectRoot: params.projectRoot }),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
this.apps.set(params.applicationId, { info: appInfo, connection });
|
|
137
|
+
this.connectionToApp.set(connection.id, params.applicationId);
|
|
138
|
+
|
|
139
|
+
this.acknowledge(connection, request);
|
|
140
|
+
|
|
141
|
+
this.emit("appRegistered", appInfo);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private handleUnregister(connection: ProtocolConnection, request: Request): void {
|
|
145
|
+
this.removeApp(connection);
|
|
146
|
+
this.acknowledge(connection, request);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private acknowledge(connection: ProtocolConnection, request: Request): void {
|
|
150
|
+
const response: Response = {
|
|
151
|
+
id: request.id,
|
|
152
|
+
result: { success: true },
|
|
153
|
+
};
|
|
154
|
+
this.connections.send(connection.id, response);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private sendError(connection: ProtocolConnection, request: Request, error: ProtocolError): void {
|
|
158
|
+
this.connections.send(connection.id, {
|
|
159
|
+
id: request.id,
|
|
160
|
+
error: error.toErrorObject(),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private removeApp(connection: ProtocolConnection): void {
|
|
165
|
+
const applicationId = this.connectionToApp.get(connection.id);
|
|
166
|
+
this.connectionToApp.delete(connection.id);
|
|
167
|
+
if (applicationId === undefined) return;
|
|
168
|
+
if (this.apps.get(applicationId)?.connection !== connection) return;
|
|
169
|
+
this.apps.delete(applicationId);
|
|
170
|
+
this.emit("appUnregistered", applicationId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import type { Socket } from "node:net";
|
|
3
|
+
import type { Message } from "./protocol/schemas.js";
|
|
4
|
+
import { type AppConnectionEvents, type AppConnections, ProtocolConnection } from "./transport.js";
|
|
5
|
+
|
|
6
|
+
export class ConnectionRegistry extends EventEmitter<AppConnectionEvents> implements AppConnections {
|
|
7
|
+
private connections: Map<string, ProtocolConnection> = new Map();
|
|
8
|
+
private sockets: Map<string, Socket> = new Map();
|
|
9
|
+
|
|
10
|
+
register(socket: Socket): ProtocolConnection {
|
|
11
|
+
const connection = ProtocolConnection.fromSocket(socket, {
|
|
12
|
+
onClose: () => {
|
|
13
|
+
this.connections.delete(connection.id);
|
|
14
|
+
this.sockets.delete(connection.id);
|
|
15
|
+
this.emit("disconnection", connection);
|
|
16
|
+
},
|
|
17
|
+
onError: (error) => this.emit("error", error),
|
|
18
|
+
});
|
|
19
|
+
this.connections.set(connection.id, connection);
|
|
20
|
+
this.sockets.set(connection.id, socket);
|
|
21
|
+
connection.on("request", (request) => this.emit("request", connection, request));
|
|
22
|
+
connection.on("invalid", ({ id: badId, error }) => {
|
|
23
|
+
connection.write({ id: badId, error: error.toErrorObject() });
|
|
24
|
+
});
|
|
25
|
+
return connection;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
send(connectionId: string, message: Message): void {
|
|
29
|
+
const connection = this.connections.get(connectionId);
|
|
30
|
+
if (!connection) return;
|
|
31
|
+
connection.write(message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dispose(reason: string): void {
|
|
35
|
+
for (const connection of this.connections.values()) {
|
|
36
|
+
connection.rejectPending(new Error(reason));
|
|
37
|
+
}
|
|
38
|
+
for (const socket of this.sockets.values()) {
|
|
39
|
+
socket.destroy();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/internal.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ErrorCode,
|
|
3
|
+
invalidRequestError,
|
|
4
|
+
methodNotFoundError,
|
|
5
|
+
ProtocolError,
|
|
6
|
+
widgetNotFoundError,
|
|
7
|
+
} from "./protocol/errors.js";
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_SOCKET_PATH,
|
|
10
|
+
type ParamsSchema,
|
|
11
|
+
type Request,
|
|
12
|
+
type SerializedWidget,
|
|
13
|
+
type ServerInitiatedMethod,
|
|
14
|
+
type ServerRequestParams,
|
|
15
|
+
ServerRequestParamsSchemas,
|
|
16
|
+
} from "./protocol/schemas.js";
|
|
17
|
+
export { ProtocolConnection } from "./transport.js";
|
package/src/protocol/errors.ts
CHANGED
|
@@ -1,61 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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,
|
|
1
|
+
export const ErrorCode = {
|
|
2
|
+
INTERNAL_ERROR: 1000,
|
|
3
|
+
NO_APP_CONNECTED: 1001,
|
|
4
|
+
APP_NOT_FOUND: 1002,
|
|
5
|
+
WIDGET_NOT_FOUND: 1003,
|
|
6
|
+
CONNECTION_WRITE_FAILED: 1004,
|
|
7
|
+
REQUEST_TIMEOUT: 1005,
|
|
8
|
+
INVALID_REQUEST: 1006,
|
|
9
|
+
METHOD_NOT_FOUND: 1007,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
13
|
+
|
|
14
|
+
export function isErrorCode(code: number): code is ErrorCode {
|
|
15
|
+
return (Object.values(ErrorCode) as number[]).includes(code);
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
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;
|
|
18
|
+
export class ProtocolError extends Error {
|
|
19
|
+
code: ErrorCode;
|
|
20
|
+
data?: unknown;
|
|
41
21
|
|
|
42
|
-
constructor(code:
|
|
22
|
+
constructor(code: ErrorCode, message: string, data?: unknown) {
|
|
43
23
|
super(message);
|
|
44
24
|
this.code = code;
|
|
45
25
|
this.data = data;
|
|
46
|
-
this.name = "
|
|
47
|
-
|
|
48
|
-
if (Error.captureStackTrace) {
|
|
49
|
-
Error.captureStackTrace(this, McpError);
|
|
50
|
-
}
|
|
26
|
+
this.name = "ProtocolError";
|
|
51
27
|
}
|
|
52
28
|
|
|
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 } {
|
|
29
|
+
toErrorObject(): { code: number; message: string; data?: unknown } {
|
|
59
30
|
return {
|
|
60
31
|
code: this.code,
|
|
61
32
|
message: this.message,
|
|
@@ -64,65 +35,38 @@ export class McpError extends Error {
|
|
|
64
35
|
}
|
|
65
36
|
}
|
|
66
37
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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" },
|
|
38
|
+
export function noAppConnectedError(): ProtocolError {
|
|
39
|
+
return new ProtocolError(
|
|
40
|
+
ErrorCode.NO_APP_CONNECTED,
|
|
41
|
+
"No GTKX application connected: start an app with 'gtkx dev' to connect",
|
|
42
|
+
{ hint: "Run 'gtkx dev' in your project directory" },
|
|
77
43
|
);
|
|
78
44
|
}
|
|
79
45
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
46
|
+
export function appNotFoundError(applicationId: string): ProtocolError {
|
|
47
|
+
return new ProtocolError(ErrorCode.APP_NOT_FOUND, `Application '${applicationId}' not found`, { applicationId });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function connectionWriteFailedError(applicationId: string): ProtocolError {
|
|
51
|
+
return new ProtocolError(
|
|
52
|
+
ErrorCode.CONNECTION_WRITE_FAILED,
|
|
53
|
+
`Connection to application '${applicationId}' is no longer writable`,
|
|
54
|
+
{ applicationId },
|
|
55
|
+
);
|
|
88
56
|
}
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
|
|
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 });
|
|
58
|
+
export function widgetNotFoundError(widgetId: string): ProtocolError {
|
|
59
|
+
return new ProtocolError(ErrorCode.WIDGET_NOT_FOUND, `Widget '${widgetId}' not found`, { widgetId });
|
|
98
60
|
}
|
|
99
61
|
|
|
100
|
-
|
|
101
|
-
|
|
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 });
|
|
62
|
+
export function requestTimeoutError(timeout: number): ProtocolError {
|
|
63
|
+
return new ProtocolError(ErrorCode.REQUEST_TIMEOUT, `Request timed out after ${timeout}ms`, { timeout });
|
|
108
64
|
}
|
|
109
65
|
|
|
110
|
-
|
|
111
|
-
|
|
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 });
|
|
66
|
+
export function invalidRequestError(reason: string): ProtocolError {
|
|
67
|
+
return new ProtocolError(ErrorCode.INVALID_REQUEST, `Invalid request: ${reason}`, { reason });
|
|
118
68
|
}
|
|
119
69
|
|
|
120
|
-
|
|
121
|
-
|
|
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 });
|
|
70
|
+
export function methodNotFoundError(method: string): ProtocolError {
|
|
71
|
+
return new ProtocolError(ErrorCode.METHOD_NOT_FOUND, `Method '${method}' not found`, { method });
|
|
128
72
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export const RequestSchema: z.ZodObject<
|
|
6
|
+
{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
method: z.ZodString;
|
|
9
|
+
params: z.ZodOptional<z.ZodUnknown>;
|
|
10
|
+
},
|
|
11
|
+
z.core.$strip
|
|
12
|
+
> = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
method: z.string(),
|
|
15
|
+
params: z.unknown().optional(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
19
|
+
|
|
20
|
+
const ErrorSchema: z.ZodObject<
|
|
21
|
+
{ code: z.ZodNumber; message: z.ZodString; data: z.ZodOptional<z.ZodUnknown> },
|
|
22
|
+
z.core.$strip
|
|
23
|
+
> = z.object({
|
|
24
|
+
code: z.number(),
|
|
25
|
+
message: z.string(),
|
|
26
|
+
data: z.unknown().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const ResponseSchema: z.ZodObject<
|
|
30
|
+
{
|
|
31
|
+
id: z.ZodString;
|
|
32
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
33
|
+
error: z.ZodOptional<typeof ErrorSchema>;
|
|
34
|
+
},
|
|
35
|
+
z.core.$strip
|
|
36
|
+
> = z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
result: z.unknown().optional(),
|
|
39
|
+
error: ErrorSchema.optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
43
|
+
|
|
44
|
+
export type SerializedWidget = {
|
|
45
|
+
id: string;
|
|
46
|
+
type: string;
|
|
47
|
+
role: string;
|
|
48
|
+
name: string | null;
|
|
49
|
+
text: string | null;
|
|
50
|
+
sensitive: boolean;
|
|
51
|
+
visible: boolean;
|
|
52
|
+
cssClasses: string[];
|
|
53
|
+
children: SerializedWidget[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type AppInfo = {
|
|
57
|
+
applicationId: string;
|
|
58
|
+
pid: number;
|
|
59
|
+
projectRoot?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const RegisterParamsSchema: z.ZodObject<
|
|
63
|
+
{
|
|
64
|
+
applicationId: z.ZodString;
|
|
65
|
+
pid: z.ZodNumber;
|
|
66
|
+
projectRoot: z.ZodOptional<z.ZodString>;
|
|
67
|
+
},
|
|
68
|
+
z.core.$strip
|
|
69
|
+
> = z.object({
|
|
70
|
+
applicationId: z.string(),
|
|
71
|
+
pid: z.number(),
|
|
72
|
+
projectRoot: z.string().optional(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const emptyParams: z.ZodObject<Record<string, never>, z.core.$strip> = z.object({});
|
|
76
|
+
export const widgetIdParams: z.ZodObject<{ widgetId: z.ZodString }, z.core.$strip> = z.object({
|
|
77
|
+
widgetId: z.string(),
|
|
78
|
+
});
|
|
79
|
+
export const queryOptionsSchema: z.ZodObject<
|
|
80
|
+
{ name: z.ZodOptional<z.ZodString>; exact: z.ZodOptional<z.ZodBoolean>; timeout: z.ZodOptional<z.ZodNumber> },
|
|
81
|
+
z.core.$strip
|
|
82
|
+
> = z.object({
|
|
83
|
+
name: z.string().optional(),
|
|
84
|
+
exact: z.boolean().optional(),
|
|
85
|
+
timeout: z.number().optional(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const queryParams: z.ZodObject<
|
|
89
|
+
{
|
|
90
|
+
by: z.ZodEnum<{ role: "role"; text: "text"; name: "name"; labelText: "labelText" }>;
|
|
91
|
+
value: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
92
|
+
options: z.ZodOptional<typeof queryOptionsSchema>;
|
|
93
|
+
},
|
|
94
|
+
z.core.$strip
|
|
95
|
+
> = z.object({
|
|
96
|
+
by: z.enum(["role", "text", "name", "labelText"]),
|
|
97
|
+
value: z.union([z.string(), z.number()]),
|
|
98
|
+
options: queryOptionsSchema.optional(),
|
|
99
|
+
});
|
|
100
|
+
export const typeParams: z.ZodObject<
|
|
101
|
+
{ widgetId: z.ZodString; text: z.ZodString; clear: z.ZodOptional<z.ZodBoolean> },
|
|
102
|
+
z.core.$strip
|
|
103
|
+
> = z.object({ widgetId: z.string(), text: z.string(), clear: z.boolean().optional() });
|
|
104
|
+
export const fireEventParams: z.ZodObject<
|
|
105
|
+
{ widgetId: z.ZodString; signal: z.ZodString; args: z.ZodOptional<z.ZodArray<z.ZodUnknown>> },
|
|
106
|
+
z.core.$strip
|
|
107
|
+
> = z.object({ widgetId: z.string(), signal: z.string(), args: z.array(z.unknown()).optional() });
|
|
108
|
+
export const screenshotParams: z.ZodObject<
|
|
109
|
+
{ windowId: z.ZodOptional<z.ZodString>; path: z.ZodOptional<z.ZodString> },
|
|
110
|
+
z.core.$strip
|
|
111
|
+
> = z.object({
|
|
112
|
+
windowId: z.string().optional(),
|
|
113
|
+
path: z.string().optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export const ServerRequestParamsSchemas: {
|
|
117
|
+
"app.getWindows": typeof emptyParams;
|
|
118
|
+
"widget.getTree": typeof emptyParams;
|
|
119
|
+
"widget.query": typeof queryParams;
|
|
120
|
+
"widget.getProps": typeof widgetIdParams;
|
|
121
|
+
"widget.click": typeof widgetIdParams;
|
|
122
|
+
"widget.type": typeof typeParams;
|
|
123
|
+
"widget.fireEvent": typeof fireEventParams;
|
|
124
|
+
"widget.screenshot": typeof screenshotParams;
|
|
125
|
+
} = {
|
|
126
|
+
"app.getWindows": emptyParams,
|
|
127
|
+
"widget.getTree": emptyParams,
|
|
128
|
+
"widget.query": queryParams,
|
|
129
|
+
"widget.getProps": widgetIdParams,
|
|
130
|
+
"widget.click": widgetIdParams,
|
|
131
|
+
"widget.type": typeParams,
|
|
132
|
+
"widget.fireEvent": fireEventParams,
|
|
133
|
+
"widget.screenshot": screenshotParams,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type ServerRequestParams<Method extends keyof typeof ServerRequestParamsSchemas> = z.infer<
|
|
137
|
+
(typeof ServerRequestParamsSchemas)[Method]
|
|
138
|
+
>;
|
|
139
|
+
|
|
140
|
+
export type ParamsSchema<Output> = z.ZodType<Output>;
|
|
141
|
+
|
|
142
|
+
export type ServerInitiatedMethod = keyof typeof ServerRequestParamsSchemas;
|
|
143
|
+
|
|
144
|
+
export type Message = Request | Response;
|
|
145
|
+
|
|
146
|
+
const getRuntimeDir = (): string => process.env.XDG_RUNTIME_DIR ?? tmpdir();
|
|
147
|
+
|
|
148
|
+
export const DEFAULT_SOCKET_PATH: string = join(getRuntimeDir(), "gtkx-mcp.sock");
|