@gtkx/mcp 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -0
- package/dist/connection-manager.d.ts +1 -0
- package/dist/connection-manager.d.ts.map +1 -0
- package/dist/connection-manager.js +1 -0
- package/dist/connection-manager.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/errors.d.ts +1 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/types.d.ts +1 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +1 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/socket-server.d.ts +1 -0
- package/dist/socket-server.d.ts.map +1 -0
- package/dist/socket-server.js +1 -0
- package/dist/socket-server.js.map +1 -0
- package/package.json +4 -2
- package/src/cli.ts +449 -0
- package/src/connection-manager.ts +247 -0
- package/src/index.ts +27 -0
- package/src/protocol/errors.ts +128 -0
- package/src/protocol/types.ts +153 -0
- package/src/socket-server.ts +194 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as net from "node:net";
|
|
4
|
+
import { invalidRequestError } from "./protocol/errors.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_SOCKET_PATH,
|
|
7
|
+
type IpcMessage,
|
|
8
|
+
type IpcRequest,
|
|
9
|
+
IpcRequestSchema,
|
|
10
|
+
type IpcResponse,
|
|
11
|
+
IpcResponseSchema,
|
|
12
|
+
} from "./protocol/types.js";
|
|
13
|
+
|
|
14
|
+
type SocketServerEventMap = {
|
|
15
|
+
connection: [AppConnection];
|
|
16
|
+
disconnection: [AppConnection];
|
|
17
|
+
request: [AppConnection, IpcRequest];
|
|
18
|
+
response: [AppConnection, IpcResponse];
|
|
19
|
+
error: [Error];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Represents a connected application.
|
|
24
|
+
*/
|
|
25
|
+
export type AppConnection = {
|
|
26
|
+
/** Unique connection identifier */
|
|
27
|
+
id: string;
|
|
28
|
+
/** The underlying socket */
|
|
29
|
+
socket: net.Socket;
|
|
30
|
+
/** Buffer for incomplete messages */
|
|
31
|
+
buffer: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Unix domain socket server for MCP communication.
|
|
36
|
+
*
|
|
37
|
+
* Manages connections from GTKX applications and handles IPC messaging.
|
|
38
|
+
*/
|
|
39
|
+
export class SocketServer extends EventEmitter<SocketServerEventMap> {
|
|
40
|
+
private server: net.Server | null = null;
|
|
41
|
+
private connections: Map<string, AppConnection> = new Map();
|
|
42
|
+
private socketPath: string;
|
|
43
|
+
|
|
44
|
+
constructor(socketPath: string = DEFAULT_SOCKET_PATH) {
|
|
45
|
+
super();
|
|
46
|
+
this.socketPath = socketPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get path(): string {
|
|
50
|
+
return this.socketPath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get isListening(): boolean {
|
|
54
|
+
return this.server?.listening ?? false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getConnections(): AppConnection[] {
|
|
58
|
+
return Array.from(this.connections.values());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getConnection(id: string): AppConnection | undefined {
|
|
62
|
+
return this.connections.get(id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async start(): Promise<void> {
|
|
66
|
+
if (this.server) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(this.socketPath)) {
|
|
71
|
+
fs.unlinkSync(this.socketPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
this.server = net.createServer((socket) => this.handleConnection(socket));
|
|
76
|
+
|
|
77
|
+
this.server.on("error", (error) => {
|
|
78
|
+
this.emit("error", error);
|
|
79
|
+
reject(error);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.server.listen(this.socketPath, () => {
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async stop(): Promise<void> {
|
|
89
|
+
if (!this.server) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const connection of this.connections.values()) {
|
|
94
|
+
connection.socket.destroy();
|
|
95
|
+
}
|
|
96
|
+
this.connections.clear();
|
|
97
|
+
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
this.server?.close(() => {
|
|
100
|
+
this.server = null;
|
|
101
|
+
if (fs.existsSync(this.socketPath)) {
|
|
102
|
+
fs.unlinkSync(this.socketPath);
|
|
103
|
+
}
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
send(connectionId: string, message: IpcMessage): boolean {
|
|
110
|
+
const connection = this.connections.get(connectionId);
|
|
111
|
+
if (!connection || !connection.socket.writable) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const data = `${JSON.stringify(message)}\n`;
|
|
116
|
+
connection.socket.write(data);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private handleConnection(socket: net.Socket): void {
|
|
121
|
+
const connectionId = crypto.randomUUID();
|
|
122
|
+
const connection: AppConnection = {
|
|
123
|
+
id: connectionId,
|
|
124
|
+
socket,
|
|
125
|
+
buffer: "",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
this.connections.set(connectionId, connection);
|
|
129
|
+
this.emit("connection", connection);
|
|
130
|
+
|
|
131
|
+
socket.on("data", (data: Buffer) => this.handleData(connection, data));
|
|
132
|
+
|
|
133
|
+
socket.on("close", () => {
|
|
134
|
+
this.connections.delete(connectionId);
|
|
135
|
+
this.emit("disconnection", connection);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
socket.on("error", (error) => {
|
|
139
|
+
this.emit("error", error);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private handleData(connection: AppConnection, data: Buffer): void {
|
|
144
|
+
connection.buffer += data.toString();
|
|
145
|
+
|
|
146
|
+
let newlineIndex = connection.buffer.indexOf("\n");
|
|
147
|
+
while (newlineIndex !== -1) {
|
|
148
|
+
const line = connection.buffer.slice(0, newlineIndex);
|
|
149
|
+
connection.buffer = connection.buffer.slice(newlineIndex + 1);
|
|
150
|
+
|
|
151
|
+
if (line.trim()) {
|
|
152
|
+
this.processMessage(connection, line);
|
|
153
|
+
}
|
|
154
|
+
newlineIndex = connection.buffer.indexOf("\n");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private processMessage(connection: AppConnection, line: string): void {
|
|
159
|
+
let parsed: unknown;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(line);
|
|
162
|
+
} catch {
|
|
163
|
+
const response: IpcResponse = {
|
|
164
|
+
id: "unknown",
|
|
165
|
+
error: invalidRequestError("Invalid JSON").toIpcError(),
|
|
166
|
+
};
|
|
167
|
+
this.send(connection.id, response);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const message = parsed as Record<string, unknown>;
|
|
172
|
+
const hasMethod = typeof message.method === "string";
|
|
173
|
+
|
|
174
|
+
if (hasMethod) {
|
|
175
|
+
const requestResult = IpcRequestSchema.safeParse(parsed);
|
|
176
|
+
if (requestResult.success) {
|
|
177
|
+
this.emit("request", connection, requestResult.data);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
const responseResult = IpcResponseSchema.safeParse(parsed);
|
|
182
|
+
if (responseResult.success) {
|
|
183
|
+
this.emit("response", connection, responseResult.data);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const response: IpcResponse = {
|
|
189
|
+
id: (message.id as string | undefined) ?? "unknown",
|
|
190
|
+
error: invalidRequestError("Invalid message format").toIpcError(),
|
|
191
|
+
};
|
|
192
|
+
this.send(connection.id, response);
|
|
193
|
+
}
|
|
194
|
+
}
|