@zooid/context-mcp 0.11.2 → 0.13.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/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zooid/context-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Daemon-side stdio MCP server + per-spawn registry that exposes zooid_get_history / zooid_get_members / zooid_get_channel_info to ACP shims, backed by each transport's TransportContextProvider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
33
33
|
"zod": "^3.23.0",
|
|
34
|
-
"@zooid/core": "^0.
|
|
34
|
+
"@zooid/core": "^0.13.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.0.0",
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect, afterEach } from 'vitest'
|
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { createConnection } from 'node:net'
|
|
5
6
|
import { SpawnRegistry } from './spawn-registry.js'
|
|
6
7
|
import { startDaemonSocketServer, callDaemon } from './daemon-socket.js'
|
|
7
8
|
import type { TransportContextProvider } from '@zooid/core'
|
|
@@ -219,3 +220,29 @@ describe('daemon-socket', () => {
|
|
|
219
220
|
}
|
|
220
221
|
})
|
|
221
222
|
})
|
|
223
|
+
|
|
224
|
+
describe('close()', () => {
|
|
225
|
+
it('does not hang on a connected client that never disconnects', async () => {
|
|
226
|
+
// The context-mcp servers spawned beside each agent outlive
|
|
227
|
+
// AcpClient.stop() (it SIGTERMs the agent and doesn't wait; these are its
|
|
228
|
+
// grandchildren). net.Server.close() waits for every open connection and,
|
|
229
|
+
// unlike http.Server, never drops idle ones — so before this was fixed,
|
|
230
|
+
// `zooid dev` hung on shutdown for as long as those processes lived.
|
|
231
|
+
const sockPath = join(tmpdir(), `zooid-close-${randomUUID()}.sock`)
|
|
232
|
+
const registry = new SpawnRegistry()
|
|
233
|
+
const handle = await startDaemonSocketServer({ sockPath, registry })
|
|
234
|
+
|
|
235
|
+
const client = createConnection(sockPath)
|
|
236
|
+
await new Promise<void>((resolve, reject) => {
|
|
237
|
+
client.once('connect', () => resolve())
|
|
238
|
+
client.once('error', reject)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const closed = await Promise.race([
|
|
242
|
+
handle.close().then(() => 'closed' as const),
|
|
243
|
+
new Promise<'hung'>((resolve) => setTimeout(() => resolve('hung'), 2000)),
|
|
244
|
+
])
|
|
245
|
+
client.destroy()
|
|
246
|
+
expect(closed).toBe('closed')
|
|
247
|
+
})
|
|
248
|
+
})
|
package/src/daemon-socket.ts
CHANGED
|
@@ -32,7 +32,17 @@ export async function startDaemonSocketServer(opts: {
|
|
|
32
32
|
registry: SpawnRegistry
|
|
33
33
|
}): Promise<DaemonSocketHandle> {
|
|
34
34
|
await unlink(opts.sockPath).catch(() => {})
|
|
35
|
+
// net.Server.close() waits for every open connection and — unlike
|
|
36
|
+
// http.Server since Node 19 — never destroys idle ones, and there is no
|
|
37
|
+
// closeAllConnections() for it. The clients here are context-mcp servers
|
|
38
|
+
// spawned beside each agent, which outlive AcpClient.stop() (it SIGTERMs
|
|
39
|
+
// the agent without waiting, and these are its grandchildren). So without
|
|
40
|
+
// tracking them, close() hangs until they happen to die — which is what
|
|
41
|
+
// made `zooid dev` take ages to stop.
|
|
42
|
+
const open = new Set<Socket>()
|
|
35
43
|
const server: Server = createServer((socket: Socket) => {
|
|
44
|
+
open.add(socket)
|
|
45
|
+
socket.on('close', () => open.delete(socket))
|
|
36
46
|
let buf = ''
|
|
37
47
|
socket.setEncoding('utf8')
|
|
38
48
|
socket.on('data', async (chunk) => {
|
|
@@ -58,6 +68,11 @@ export async function startDaemonSocketServer(opts: {
|
|
|
58
68
|
close: () =>
|
|
59
69
|
new Promise<void>((resolve) => {
|
|
60
70
|
server.close(() => resolve())
|
|
71
|
+
// Shutdown is already tearing the agents down, so there is no
|
|
72
|
+
// in-flight request worth waiting for. Drop the connections that
|
|
73
|
+
// would otherwise keep close() pending forever.
|
|
74
|
+
for (const socket of open) socket.destroy()
|
|
75
|
+
open.clear()
|
|
61
76
|
}),
|
|
62
77
|
}
|
|
63
78
|
}
|