@gtkx/mcp 2.0.0-beta.1 → 2.0.0-beta.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/app-router.d.ts +6 -2
- package/dist/app-router.d.ts.map +1 -1
- package/dist/app-router.js +56 -19
- package/dist/app-router.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -1
- package/dist/internal.js.map +1 -1
- package/dist/protocol/schemas.d.ts +1 -2
- package/dist/protocol/schemas.d.ts.map +1 -1
- package/dist/protocol/schemas.js +1 -7
- package/dist/protocol/schemas.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +86 -39
- package/dist/server.js.map +1 -1
- package/dist/socket-path.d.ts +11 -0
- package/dist/socket-path.d.ts.map +1 -0
- package/dist/socket-path.js +74 -0
- package/dist/socket-path.js.map +1 -0
- package/dist/socket-server.d.ts +3 -1
- package/dist/socket-server.d.ts.map +1 -1
- package/dist/socket-server.js +48 -32
- package/dist/socket-server.js.map +1 -1
- package/package.json +5 -5
- package/src/app-router.ts +77 -22
- package/src/internal.ts +4 -1
- package/src/protocol/schemas.ts +0 -9
- package/src/server.ts +107 -41
- package/src/socket-path.ts +103 -0
- package/src/socket-server.ts +61 -37
package/src/socket-server.ts
CHANGED
|
@@ -3,11 +3,16 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as net from "node:net";
|
|
4
4
|
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
|
5
5
|
import type { ConnectionRegistry } from "./connection-registry.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
cleanupMcpSocketAddress,
|
|
8
|
+
type McpSocketAddress,
|
|
9
|
+
prepareMcpSocketAddress,
|
|
10
|
+
resolveMcpSocketAddress,
|
|
11
|
+
} from "./socket-path.js";
|
|
7
12
|
import { connectionErrorEvent } from "./transport.js";
|
|
8
13
|
|
|
9
14
|
type ProbeOutcome = { kind: "live" } | { kind: "unknown"; code: string } | { kind: "vacant" };
|
|
10
|
-
type PathVerdict = ProbeOutcome | { kind: "
|
|
15
|
+
type PathVerdict = ProbeOutcome | { kind: "invalid" };
|
|
11
16
|
type ClaimOutcome = "occupied" | "published";
|
|
12
17
|
|
|
13
18
|
const PROBE_TIMEOUT_MS = 1000;
|
|
@@ -75,12 +80,6 @@ const acquireClaimLock = async (socketPath: string): Promise<net.Server | null>
|
|
|
75
80
|
return lock;
|
|
76
81
|
};
|
|
77
82
|
|
|
78
|
-
const releaseClaimLock = async (lock: net.Server | null): Promise<void> => {
|
|
79
|
-
if (lock) {
|
|
80
|
-
await closeServer(lock);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
83
|
const withClaimLock = async <T>(socketPath: string, action: () => Promise<T> | T): Promise<T> => {
|
|
85
84
|
const lock = await acquireClaimLock(socketPath);
|
|
86
85
|
|
|
@@ -159,10 +158,10 @@ const undecidedOwnerError = (socketPath: string, code: string): Error =>
|
|
|
159
158
|
"Retry, or delete the file by hand once no server is running.",
|
|
160
159
|
);
|
|
161
160
|
|
|
162
|
-
const
|
|
161
|
+
const invalidPathError = (socketPath: string): Error =>
|
|
163
162
|
new Error(
|
|
164
|
-
`The GTKX MCP socket path ${socketPath}
|
|
165
|
-
"
|
|
163
|
+
`The GTKX MCP socket path ${socketPath} exists and is not a socket. ` +
|
|
164
|
+
"Move it, or point XDG_RUNTIME_DIR at a directory where GTKX can create its socket.",
|
|
166
165
|
);
|
|
167
166
|
|
|
168
167
|
const listenFailureError = (socketPath: string, code: string): Error =>
|
|
@@ -184,8 +183,8 @@ const clearStalePath = async (target: string): Promise<PathVerdict> => {
|
|
|
184
183
|
return { kind: "vacant" };
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
if (entry.
|
|
188
|
-
return { kind: "
|
|
186
|
+
if (!entry.isSocket()) {
|
|
187
|
+
return { kind: "invalid" };
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
const outcome = await probeUntilConclusive(target);
|
|
@@ -204,8 +203,8 @@ const requireVacantPath = async (socketPath: string): Promise<void> => {
|
|
|
204
203
|
throw alreadyOwnedError(socketPath);
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
if (verdict.kind === "
|
|
208
|
-
throw
|
|
206
|
+
if (verdict.kind === "invalid") {
|
|
207
|
+
throw invalidPathError(socketPath);
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
if (verdict.kind === "unknown") {
|
|
@@ -242,26 +241,18 @@ const publishSocket = async (privatePath: string, socketPath: string): Promise<n
|
|
|
242
241
|
throw alreadyOwnedError(socketPath);
|
|
243
242
|
};
|
|
244
243
|
|
|
245
|
-
const releaseSocketPath = async (socketPath: string, inode: number): Promise<void> => {
|
|
246
|
-
const lock = await acquireClaimLock(socketPath);
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
removeEntry(socketPath, inode);
|
|
250
|
-
} finally {
|
|
251
|
-
await releaseClaimLock(lock);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
244
|
class SocketServer {
|
|
256
245
|
private server: net.Server | null = null;
|
|
257
246
|
private socketPath: string;
|
|
258
247
|
private registry: ConnectionRegistry;
|
|
248
|
+
private address: McpSocketAddress;
|
|
259
249
|
private boundInode: number | null = null;
|
|
260
250
|
private startup: Promise<void> | null = null;
|
|
261
251
|
|
|
262
|
-
constructor(registry: ConnectionRegistry,
|
|
252
|
+
constructor(registry: ConnectionRegistry, address: McpSocketAddress = resolveMcpSocketAddress()) {
|
|
263
253
|
this.registry = registry;
|
|
264
|
-
this.
|
|
254
|
+
this.address = address;
|
|
255
|
+
this.socketPath = address.path;
|
|
265
256
|
}
|
|
266
257
|
|
|
267
258
|
private listen(privatePath: string): Promise<net.Server> {
|
|
@@ -294,21 +285,46 @@ class SocketServer {
|
|
|
294
285
|
}
|
|
295
286
|
|
|
296
287
|
private async bind(): Promise<void> {
|
|
288
|
+
prepareMcpSocketAddress(this.address);
|
|
297
289
|
const privatePath = privatePathFor(this.socketPath);
|
|
298
290
|
await clearStalePath(privatePath);
|
|
299
291
|
const server = await this.listenPrivately(privatePath);
|
|
292
|
+
const privateInode = inodeFor(privatePath);
|
|
300
293
|
|
|
301
294
|
try {
|
|
302
|
-
|
|
295
|
+
if (privateInode === null) {
|
|
296
|
+
throw listenFailureError(this.socketPath, "ENOENT");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const publishedInode = await publishSocket(privatePath, this.socketPath);
|
|
300
|
+
|
|
301
|
+
if (publishedInode !== privateInode) {
|
|
302
|
+
throw listenFailureError(this.socketPath, "ESTALE");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.boundInode = privateInode;
|
|
303
306
|
this.server = server;
|
|
304
307
|
} catch (error) {
|
|
305
308
|
await closeServer(server);
|
|
309
|
+
|
|
310
|
+
if (privateInode !== null) {
|
|
311
|
+
removeEntry(privatePath, privateInode);
|
|
312
|
+
removeEntry(this.socketPath, privateInode);
|
|
313
|
+
}
|
|
314
|
+
|
|
306
315
|
throw error;
|
|
307
316
|
}
|
|
308
317
|
}
|
|
309
318
|
|
|
310
319
|
private open(): Promise<void> {
|
|
311
|
-
return withClaimLock(this.socketPath, () =>
|
|
320
|
+
return withClaimLock(this.socketPath, async () => {
|
|
321
|
+
try {
|
|
322
|
+
await this.bind();
|
|
323
|
+
} catch (error) {
|
|
324
|
+
cleanupMcpSocketAddress(this.address);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
312
328
|
}
|
|
313
329
|
|
|
314
330
|
private async settleStartup(): Promise<void> {
|
|
@@ -320,13 +336,23 @@ class SocketServer {
|
|
|
320
336
|
}
|
|
321
337
|
}
|
|
322
338
|
|
|
323
|
-
private async release(): Promise<void> {
|
|
339
|
+
private async release(server: net.Server): Promise<void> {
|
|
324
340
|
const inode = this.boundInode;
|
|
325
|
-
this.boundInode = null;
|
|
326
341
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
342
|
+
await withClaimLock(this.socketPath, async () => {
|
|
343
|
+
this.registry.dispose();
|
|
344
|
+
await closeServer(server);
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
if (inode !== null) {
|
|
348
|
+
removeEntry(this.socketPath, inode);
|
|
349
|
+
}
|
|
350
|
+
} finally {
|
|
351
|
+
cleanupMcpSocketAddress(this.address);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
this.boundInode = null;
|
|
330
356
|
}
|
|
331
357
|
|
|
332
358
|
async start(): Promise<void> {
|
|
@@ -352,10 +378,8 @@ class SocketServer {
|
|
|
352
378
|
return;
|
|
353
379
|
}
|
|
354
380
|
|
|
381
|
+
await this.release(server);
|
|
355
382
|
this.server = null;
|
|
356
|
-
this.registry.dispose();
|
|
357
|
-
await closeServer(server);
|
|
358
|
-
await this.release();
|
|
359
383
|
}
|
|
360
384
|
}
|
|
361
385
|
|