@mohou/mcp-client 1.0.33 → 1.0.34

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/client.ts +29 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohou/mcp-client",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
package/src/client.ts CHANGED
@@ -218,12 +218,20 @@ export class McpClient {
218
218
  if (current?.generation === generation) this.sessions.delete(serverId)
219
219
  if (!this.recovering.has(serverId)) this.dropped.add(serverId)
220
220
  }
221
+ const transport = transportFor(spec, scrubEnv(this.parentEnv)) as Transport
222
+ const stderr = collectStderr(transport)
221
223
  try {
222
- await client.connect(transportFor(spec, scrubEnv(this.parentEnv)) as Transport)
224
+ await client.connect(transport)
223
225
  return { client, generation }
224
226
  } catch (error) {
225
227
  await client.close().catch(() => undefined)
226
- throw new McpError('mcp-start-failed', this.redact(error instanceof Error ? error.message : 'mcp server did not start', spec), { cause: error })
228
+ // A stdio server that exits reports nothing but "Connection closed", and its own last words
229
+ // are on stderr. Give the pipe a moment: they often land just after the socket closes.
230
+ await new Promise(resolve => setTimeout(resolve, STDERR_GRACE_MS))
231
+ const said = stderr.read()
232
+ const base = error instanceof Error ? error.message : 'mcp server did not start'
233
+ const message = said === undefined ? base : `${base} — ${said}`
234
+ throw new McpError('mcp-start-failed', this.redact(message, spec), { cause: error })
227
235
  }
228
236
  }
229
237
 
@@ -288,6 +296,25 @@ function transportFor(spec: McpServerSpec, parent: Record<string, string>) {
288
296
  })
289
297
  }
290
298
 
299
+ /** The child's own last words, capped: a server that dies says why on stderr, and nothing else does. */
300
+ const STDERR_TAIL_CHARS = 600
301
+ const STDERR_GRACE_MS = 100
302
+
303
+ function collectStderr(transport: Transport): { read(): string | undefined } {
304
+ const stream = (transport as { stderr?: NodeJS.ReadableStream | null }).stderr
305
+ if (stream === null || stream === undefined) return { read: () => undefined }
306
+ let tail = ''
307
+ stream.on('data', (chunk: Buffer | string) => {
308
+ tail = `${tail}${chunk.toString()}`.slice(-STDERR_TAIL_CHARS)
309
+ })
310
+ return {
311
+ read() {
312
+ const text = tail.trim()
313
+ return text.length === 0 ? undefined : text
314
+ },
315
+ }
316
+ }
317
+
291
318
  function scrubEnv(env: NodeJS.ProcessEnv): Record<string, string> {
292
319
  const next: Record<string, string> = {}
293
320
  for (const [key, value] of Object.entries(env)) {