@humain/terminal 0.0.14 → 0.0.16

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 (41) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/bundle/chunks/{chunk-6TW5AYGH.js → chunk-Z4RA2T5Q.js} +252 -40
  3. package/dist/bundle/cli.js +1 -1
  4. package/dist/bundle/index.js +1 -1
  5. package/dist/bundle/rpc-entry.js +1 -1
  6. package/dist/core/tools/subagent.d.ts.map +1 -1
  7. package/dist/core/tools/subagent.js +30 -9
  8. package/dist/core/tools/subagent.js.map +1 -1
  9. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
  10. package/dist/humain/mcp-adapter/vendor/README.md +13 -13
  11. package/dist/humain/mcp-adapter/vendor/commands.ts +58 -21
  12. package/dist/humain/mcp-adapter/vendor/config.ts +18 -4
  13. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +26 -6
  14. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +4 -0
  15. package/dist/humain/mcp-adapter/vendor/dist/config.js +13 -4
  16. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  17. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +11 -3
  18. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  19. package/dist/humain/mcp-adapter/vendor/error-signal.ts +15 -4
  20. package/dist/humain/mcp-adapter/vendor/errors.ts +141 -0
  21. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +58 -5
  22. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1017 -160
  23. package/dist/humain/mcp-adapter/vendor/index.ts +2 -3
  24. package/dist/humain/mcp-adapter/vendor/init.ts +5 -0
  25. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +30 -9
  26. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +66 -28
  27. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +2 -0
  28. package/dist/humain/mcp-adapter/vendor/package.json +3 -2
  29. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +66 -13
  30. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +217 -0
  31. package/dist/humain/mcp-adapter/vendor/server-manager.ts +337 -6
  32. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +1 -0
  33. package/dist/humain/mcp-adapter/vendor/types.ts +18 -3
  34. package/dist/humain/mcp-adapter/vendor/ui-resource-handler.ts +18 -2
  35. package/dist/humain/mcp-adapter/vendor/ui-server.ts +179 -54
  36. package/dist/humain/mcp-adapter/vendor/ui-session.ts +28 -8
  37. package/npm-shrinkwrap.json +2 -2
  38. package/package.json +1 -1
  39. package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
  40. package/src/humain/mcp-adapter/upstream.json +4 -4
  41. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
@@ -12,6 +12,11 @@ import type { ConsentManager } from "./consent-manager.ts";
12
12
  import { ServerError, wrapError } from "./errors.ts";
13
13
  import { formatAuthRequiredMessage, normalizeToolArguments } from "./utils.ts";
14
14
  import { buildHostHtmlTemplate, buildCspMetaContent } from "./host-html-template.ts";
15
+ import {
16
+ buildSandboxProxyCsp,
17
+ buildSandboxProxyHtml,
18
+ SANDBOX_PROXY_PATH,
19
+ } from "./sandbox-proxy-template.ts";
15
20
  import { logger } from "./logger.ts";
16
21
  import type { McpServerManager } from "./server-manager.ts";
17
22
  import type { McpExtensionState } from "./state.ts";
@@ -99,6 +104,11 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
99
104
  const eventLog: Array<{ id: number; name: string; payload: unknown }> = [];
100
105
  let latestCheckpointEventId: number | undefined;
101
106
  let streamSummary: UiStreamSummary | undefined;
107
+ let server: http.Server | null = null;
108
+ let sandboxProxyServer: http.Server | null = null;
109
+ let sandboxProxyUrl: string | null = null;
110
+ let closeTimer: NodeJS.Timeout | null = null;
111
+ let listenersClosed = false;
102
112
 
103
113
  // Track messages from UI for retrieval
104
114
  const sessionMessages: UiSessionMessages = {
@@ -266,6 +276,32 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
266
276
  watchdog = null;
267
277
  };
268
278
 
279
+ const closeListeners = () => {
280
+ if (listenersClosed) return;
281
+ listenersClosed = true;
282
+ stopWatchdog();
283
+ if (closeTimer) {
284
+ clearTimeout(closeTimer);
285
+ closeTimer = null;
286
+ }
287
+ try {
288
+ server?.close();
289
+ } catch {}
290
+ try {
291
+ sandboxProxyServer?.close();
292
+ } catch {}
293
+ closeSse();
294
+ };
295
+
296
+ const scheduleListenerClose = () => {
297
+ if (closeTimer || listenersClosed) return;
298
+ closeTimer = setTimeout(() => {
299
+ closeTimer = null;
300
+ closeListeners();
301
+ }, 20);
302
+ closeTimer.unref();
303
+ };
304
+
269
305
  const markCompleted = (reason: string) => {
270
306
  if (completed) return;
271
307
  log.debug("Session completed", { reason });
@@ -273,9 +309,10 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
273
309
  completed = true;
274
310
  stopWatchdog();
275
311
  options.onComplete?.(reason);
312
+ scheduleListenerClose();
276
313
  };
277
314
 
278
- const server = http.createServer(async (req, res) => {
315
+ const hostServer = http.createServer(async (req, res) => {
279
316
  try {
280
317
  const method = req.method || "GET";
281
318
  const hostHeader = req.headers.host;
@@ -302,6 +339,10 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
302
339
  }
303
340
  if (!validateTokenQuery(url, sessionToken, res)) return;
304
341
  touchHeartbeat();
342
+ if (!sandboxProxyUrl) {
343
+ sendText(res, 503, "Sandbox proxy is not ready");
344
+ return;
345
+ }
305
346
 
306
347
  const html = buildHostHtmlTemplate({
307
348
  sessionToken,
@@ -314,6 +355,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
314
355
  requireToolConsent: options.consentManager.requiresPrompt(options.serverName),
315
356
  cacheToolConsent: options.consentManager.shouldCacheConsent(),
316
357
  hostContext,
358
+ sandboxProxyUrl,
317
359
  });
318
360
 
319
361
  res.writeHead(200, {
@@ -498,9 +540,15 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
498
540
  ...(options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}),
499
541
  },
500
542
  options.serverName,
501
- (conn) => conn.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName)),
543
+ async (conn) => {
544
+ await options.manager.ensureListen?.(options.serverName, conn);
545
+ return conn.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
546
+ },
502
547
  )
503
- : await connection.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
548
+ : await (async () => {
549
+ await options.manager.ensureListen?.(options.serverName, connection);
550
+ return connection.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
551
+ })();
504
552
  sendJson(res, 200, { ok: true, result });
505
553
  } finally {
506
554
  options.manager.decrementInFlight(options.serverName);
@@ -614,12 +662,6 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
614
662
  : "done";
615
663
  markCompleted(reason);
616
664
  sendJson(res, 200, { ok: true, result: {} });
617
- setTimeout(() => {
618
- try {
619
- server.close();
620
- } catch {}
621
- closeSse();
622
- }, 20).unref();
623
665
  return;
624
666
  }
625
667
 
@@ -641,6 +683,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
641
683
  sendJson(res, status, { ok: false, error: wrapped.message });
642
684
  }
643
685
  });
686
+ server = hostServer;
644
687
 
645
688
  if (options.initialResultPromise) {
646
689
  options.initialResultPromise.then(
@@ -656,10 +699,6 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
656
699
  if (completed) return;
657
700
  if (Date.now() - lastHeartbeatAt <= ABANDONED_GRACE_MS) return;
658
701
  markCompleted("stale");
659
- try {
660
- server.close();
661
- } catch {}
662
- closeSse();
663
702
  }, WATCHDOG_INTERVAL_MS);
664
703
  watchdog.unref();
665
704
 
@@ -667,13 +706,79 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
667
706
  const candidates = resolvePortCandidates(options.port);
668
707
  let candidateIndex = 0;
669
708
 
709
+ const startSandboxProxy = (parentOrigin: string): Promise<number> => {
710
+ const proxy = http.createServer((req, res) => {
711
+ try {
712
+ const method = req.method || "GET";
713
+ const hostHeader = req.headers.host;
714
+ const url = new URL(req.url || "/", `http://${hostHeader || "127.0.0.1"}`);
715
+ if (hostHeader !== undefined && !isAllowedHost(url.hostname)) {
716
+ sendText(res, 403, "Invalid host");
717
+ return;
718
+ }
719
+
720
+ if (method === "HEAD" && url.pathname === SANDBOX_PROXY_PATH) {
721
+ res.writeHead(200, {
722
+ "Content-Type": "text/html; charset=utf-8",
723
+ "Cache-Control": "no-store",
724
+ "Content-Security-Policy": buildSandboxProxyCsp(),
725
+ });
726
+ res.end();
727
+ return;
728
+ }
729
+
730
+ if (method === "GET" && url.pathname === SANDBOX_PROXY_PATH) {
731
+ res.writeHead(200, {
732
+ "Content-Type": "text/html; charset=utf-8",
733
+ "Cache-Control": "no-store",
734
+ "Content-Security-Policy": buildSandboxProxyCsp(),
735
+ "Referrer-Policy": "no-referrer",
736
+ "X-Content-Type-Options": "nosniff",
737
+ });
738
+ res.end(buildSandboxProxyHtml({ parentOrigin }));
739
+ return;
740
+ }
741
+
742
+ sendJson(res, 404, { ok: false, error: "Not found" });
743
+ } catch (error) {
744
+ sendJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
745
+ }
746
+ });
747
+ sandboxProxyServer = proxy;
748
+
749
+ return new Promise((resolveProxy, rejectProxy) => {
750
+ const onError = (error: NodeJS.ErrnoException) => {
751
+ proxy.off("listening", onListening);
752
+ rejectProxy(error);
753
+ };
754
+ const onListening = () => {
755
+ proxy.off("error", onError);
756
+ const address = proxy.address();
757
+ if (!address || typeof address === "string") {
758
+ rejectProxy(new ServerError("invalid sandbox proxy address"));
759
+ return;
760
+ }
761
+ resolveProxy(address.port);
762
+ };
763
+ proxy.once("error", onError);
764
+ proxy.listen(0, "127.0.0.1", onListening);
765
+ });
766
+ };
767
+
670
768
  const listen = () => {
671
- server.once("error", onError);
672
- server.listen(candidates[candidateIndex], "127.0.0.1", onListening);
769
+ const candidate = candidates[candidateIndex];
770
+ if (candidate === undefined) {
771
+ const error = new ServerError("no UI server port candidates available");
772
+ closeListeners();
773
+ reject(error);
774
+ return;
775
+ }
776
+ hostServer.once("error", onError);
777
+ hostServer.listen(candidate, "127.0.0.1", onListening);
673
778
  };
674
779
 
675
780
  const onError = (error: NodeJS.ErrnoException) => {
676
- server.off("listening", onListening);
781
+ hostServer.off("listening", onListening);
677
782
  if (error.code === "EADDRINUSE" && candidateIndex < candidates.length - 1) {
678
783
  candidateIndex += 1;
679
784
  listen();
@@ -681,6 +786,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
681
786
  }
682
787
  log.error("Failed to start server", error);
683
788
  const port = candidates[candidateIndex];
789
+ closeListeners();
684
790
  reject(new ServerError(error.message, {
685
791
  ...(port !== undefined ? { port } : {}),
686
792
  cause: error,
@@ -688,52 +794,71 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
688
794
  };
689
795
 
690
796
  const onListening = () => {
691
- server.off("error", onError);
692
- const address = server.address();
797
+ hostServer.off("error", onError);
798
+ const address = hostServer.address();
693
799
  if (!address || typeof address === "string") {
694
800
  const err = new ServerError("invalid address");
695
801
  log.error("Invalid server address", err);
802
+ closeListeners();
696
803
  reject(err);
697
804
  return;
698
805
  }
699
806
 
700
- log.debug("Server started", { port: address.port });
701
- rememberMoshiDiscoveryPort(address.port);
702
-
703
- const handle: UiServerHandle = {
704
- url: `http://localhost:${address.port}/?session=${sessionToken}`,
705
- port: address.port,
706
- sessionToken,
707
- serverName: options.serverName,
708
- toolName: options.toolName,
709
- close: (reason?: string) => {
710
- markCompleted(reason ?? "closed");
711
- try {
712
- server.close();
713
- } catch {}
714
- closeSse();
715
- },
716
- sendToolInput: (args: Record<string, unknown>) => {
717
- pushEvent("tool-input", { arguments: args });
718
- },
719
- sendToolResult: (result: CallToolResult) => {
720
- pushEvent("tool-result", result);
721
- },
722
- sendResultPatch: (result: CallToolResult) => {
723
- pushEvent("result-patch", result);
724
- },
725
- sendToolCancelled: (reason: string) => {
726
- pushEvent("tool-cancelled", { reason });
727
- },
728
- sendHostContext: (context: UiHostContext) => {
729
- Object.assign(hostContext, context);
730
- pushEvent("host-context", context);
731
- },
732
- getSessionMessages: () => ({ ...sessionMessages }),
733
- getStreamSummary: () => streamSummary ? { ...streamSummary, phases: [...streamSummary.phases] } : undefined,
734
- };
807
+ const parentOrigin = `http://localhost:${address.port}`;
808
+ void startSandboxProxy(parentOrigin).then((proxyPort) => {
809
+ if (completed || listenersClosed) {
810
+ closeListeners();
811
+ reject(new ServerError("UI session completed before sandbox proxy was ready"));
812
+ return;
813
+ }
814
+ sandboxProxyUrl = `http://localhost:${proxyPort}${SANDBOX_PROXY_PATH}`;
815
+ log.debug("Servers started", { port: address.port, proxyPort });
816
+ rememberMoshiDiscoveryPort(address.port);
817
+
818
+ const handle: UiServerHandle = {
819
+ url: `http://localhost:${address.port}/?session=${sessionToken}`,
820
+ port: address.port,
821
+ proxyUrl: sandboxProxyUrl,
822
+ proxyPort,
823
+ sessionToken,
824
+ serverName: options.serverName,
825
+ toolName: options.toolName,
826
+ close: (reason?: string) => {
827
+ markCompleted(reason ?? "closed");
828
+ closeListeners();
829
+ },
830
+ sendToolInput: (args: Record<string, unknown>) => {
831
+ pushEvent("tool-input", { arguments: args });
832
+ },
833
+ sendToolResult: (result: CallToolResult) => {
834
+ pushEvent("tool-result", result);
835
+ },
836
+ sendResultPatch: (result: CallToolResult) => {
837
+ pushEvent("result-patch", result);
838
+ },
839
+ sendToolCancelled: (reason: string) => {
840
+ pushEvent("tool-cancelled", { reason });
841
+ },
842
+ sendResourceUpdated: (uri: string) => {
843
+ pushEvent("resource-updated", { uri });
844
+ },
845
+ sendHostContext: (context: UiHostContext) => {
846
+ Object.assign(hostContext, context);
847
+ pushEvent("host-context", context);
848
+ },
849
+ getSessionMessages: () => ({ ...sessionMessages }),
850
+ getStreamSummary: () => streamSummary ? { ...streamSummary, phases: [...streamSummary.phases] } : undefined,
851
+ };
735
852
 
736
- resolve(handle);
853
+ resolve(handle);
854
+ }).catch((error) => {
855
+ log.error("Failed to start sandbox proxy", error instanceof Error ? error : undefined);
856
+ closeListeners();
857
+ const wrapped = error instanceof ServerError
858
+ ? error
859
+ : new ServerError(error instanceof Error ? error.message : String(error), { cause: error });
860
+ reject(wrapped);
861
+ });
737
862
  };
738
863
 
739
864
  listen();
@@ -21,6 +21,7 @@ import { isGlimpseAvailable, openGlimpseWindow } from "./glimpse-ui.ts";
21
21
  import type { SessionRecoveryDeps } from "./session-recovery.ts";
22
22
  import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
23
23
  import { throwIfAborted } from "./abort.ts";
24
+ import { InputRequiredNeedsUiError } from "./errors.ts";
24
25
 
25
26
  let activeGlimpseWindow: { close(): void } | null = null;
26
27
 
@@ -172,7 +173,7 @@ function probeMoshiGateway(): Promise<boolean> {
172
173
  });
173
174
  }
174
175
 
175
- function remoteAccessHint(opts: { url: string; port: number; moshi: boolean; openError: string | null; openedOnHost?: boolean }): string {
176
+ function remoteAccessHint(opts: { url: string; port: number; proxyPort: number; moshi: boolean; openError: string | null; openedOnHost?: boolean }): string {
176
177
  const lines = [
177
178
  opts.openError !== null
178
179
  ? "Couldn't open MCP UI here. Open it from your own device:"
@@ -185,10 +186,12 @@ function remoteAccessHint(opts: { url: string; port: number; moshi: boolean; ope
185
186
  lines.push(`Browser launch failed: ${opts.openError}`);
186
187
  }
187
188
  if (opts.moshi) {
188
- lines.push("Moshi: tap the preview button in the terminal title bar and pick this MCP UI server.");
189
+ lines.push(
190
+ `Moshi: tap the preview button in the terminal title bar and pick this MCP UI server (it must reach ports ${opts.port} and ${opts.proxyPort}).`,
191
+ );
189
192
  }
190
193
  lines.push(
191
- `SSH: run \`ssh -L ${opts.port}:127.0.0.1:${opts.port} <this-host>\` on your local machine, then open the URL above.`,
194
+ `SSH: run \`ssh -L ${opts.port}:127.0.0.1:${opts.port} -L ${opts.proxyPort}:127.0.0.1:${opts.proxyPort} <this-host>\` on your local machine, then open the URL above.`,
192
195
  "mosh can't forward ports - run that ssh command in a separate terminal.",
193
196
  );
194
197
  return lines.join("\n");
@@ -314,11 +317,13 @@ export async function maybeStartUiSession(
314
317
  let active = true;
315
318
  let nextStreamSequence = 0;
316
319
  let handle: UiServerHandle;
320
+ const resourceListenerToken = randomUUID();
317
321
 
318
- const cleanupStreamListener = () => {
322
+ const cleanupListeners = () => {
319
323
  if (streamToken) {
320
324
  state.manager.removeUiStreamListener(streamToken);
321
325
  }
326
+ state.manager.removeResourceUpdatedListener?.(resourceListenerToken);
322
327
  };
323
328
 
324
329
  handle = await startUiServer({
@@ -394,7 +399,7 @@ export async function maybeStartUiSession(
394
399
 
395
400
  onComplete: (reason: string) => {
396
401
  active = false;
397
- cleanupStreamListener();
402
+ cleanupListeners();
398
403
 
399
404
  if (state.uiServer === handle) {
400
405
  const messages = handle.getSessionMessages();
@@ -454,6 +459,16 @@ export async function maybeStartUiSession(
454
459
  });
455
460
  }
456
461
 
462
+ state.manager.registerResourceUpdatedListener?.(
463
+ resourceListenerToken,
464
+ request.serverName,
465
+ request.uiResourceUri,
466
+ (_serverName, uri) => {
467
+ if (!active || state.uiServer !== handle) return;
468
+ handle.sendResourceUpdated(uri);
469
+ },
470
+ );
471
+
457
472
  state.uiServer = handle;
458
473
 
459
474
  const viewerPref = process.env.MCP_UI_VIEWER?.toLowerCase();
@@ -466,7 +481,11 @@ export async function maybeStartUiSession(
466
481
  if (uiSuppressed) {
467
482
  viewer = "suppressed";
468
483
  windowOpen = false;
469
- state.ui?.notify(`MCP UI window suppressed (MCP_UI_VIEWER=${viewerPref}). Open manually: ${handle.url}`, "info");
484
+ state.ui?.notify(
485
+ `MCP UI window suppressed (MCP_UI_VIEWER=${viewerPref}). Open manually: ${handle.url}\n` +
486
+ `If this session is remote, run ssh -L ${handle.port}:127.0.0.1:${handle.port} -L ${handle.proxyPort}:127.0.0.1:${handle.proxyPort} <this-host> first.`,
487
+ "info",
488
+ );
470
489
  log.info("Suppressing MCP UI window (MCP_UI_VIEWER=" + viewerPref + ")", { url: handle.url });
471
490
  } else {
472
491
  const remoteLikely = remoteByEnv || await hasActiveRemoteLogin();
@@ -474,6 +493,7 @@ export async function maybeStartUiSession(
474
493
  state.ui?.notify(remoteAccessHint({
475
494
  url: handle.url,
476
495
  port: handle.port,
496
+ proxyPort: handle.proxyPort,
477
497
  moshi: await probeMoshiGateway(),
478
498
  openError,
479
499
  openedOnHost,
@@ -554,12 +574,12 @@ export async function maybeStartUiSession(
554
574
  },
555
575
  close: (reason?: string) => {
556
576
  active = false;
557
- cleanupStreamListener();
577
+ cleanupListeners();
558
578
  handle.close(reason);
559
579
  },
560
580
  };
561
581
  } catch (error) {
562
- if (error instanceof UrlElicitationRequiredError || isAbortError(error, runtimeSignal)) throw error;
582
+ if (error instanceof UrlElicitationRequiredError || error instanceof InputRequiredNeedsUiError || isAbortError(error, runtimeSignal)) throw error;
563
583
  const message = error instanceof Error ? error.message : String(error);
564
584
  log.error("Failed to start UI session", error instanceof Error ? error : undefined);
565
585
  state.ui?.notify(
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@humain/terminal",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@humain/terminal",
9
- "version": "0.0.14",
9
+ "version": "0.0.16",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@humain/terminal-contracts": "0.0.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humain/terminal",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "humainTerminalConfig": {
@@ -1,7 +1,7 @@
1
1
  # pi-mcp-adapter upstream tracking
2
2
 
3
3
  HUMAIN Terminal vendors [`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter)
4
- version `2.31.0` so MCP support is available without a separate VM installation.
4
+ version `2.32.1` so MCP support is available without a separate VM installation.
5
5
 
6
6
  The unmodified upstream package is recorded in `upstream.json`, including the
7
7
  registry integrity and source `gitHead`. The release currentness gate resolves
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
3
  "repository": "https://github.com/nicobailon/pi-mcp-adapter",
4
- "version": "2.31.0",
5
- "tarball": "https://registry.npmjs.org/pi-mcp-adapter/-/pi-mcp-adapter-2.31.0.tgz",
6
- "integrity": "sha512-yKPgHWFLQY6FT7ukCgEp5KvkAZ4ZC1pl7+8Tdq+j4evfTtjhHXPuIpi0joNN7lIIKuJD1cjlTb+hu6IxRpseoA==",
7
- "gitHead": "eca6e8e14746f2a093e5d3085ccc244832962d71"
4
+ "version": "2.32.1",
5
+ "tarball": "https://registry.npmjs.org/pi-mcp-adapter/-/pi-mcp-adapter-2.32.1.tgz",
6
+ "integrity": "sha512-GNLYa2U9T5ZqIhZmhx/RTenEjfakJTelq/z6Q+At5SIxyuYvrvobriDEVsnx+lqetVDizUCudTWLfdZytQu0rg==",
7
+ "gitHead": "10a45367e033a32026987a75d6f401e37340c86f"
8
8
  }
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.32.1] - 2026-09-01
11
+
12
+ ### Fixed
13
+ - The published package now includes the updated public helper build artifacts for the `2.32.0` config and MCP Apps changes.
14
+
15
+ ## [2.32.0] - 2026-09-01
16
+
17
+ ### Highlights
18
+ - You can now enable and disable MCP servers directly from the `/mcp` panel.
19
+ - `/mcp setup` is clearer about where new shared servers will be saved.
20
+ - MCP App views that use browser storage now render reliably without exposing host session access.
21
+ - Long-running sessions handle MCP 2026 input flows, catalog updates, and UI resource refreshes more reliably.
22
+ - OAuth reuse and per-server status messages are less confusing.
23
+
24
+ ### Added
25
+ - The `/mcp` panel now supports enabling and disabling servers in place with `ctrl+d` on a server row. Saving persists the `disabled` flag to the project Pi layer and reloads the session, matching `/mcp disable` and `/mcp enable`. Thanks to [@ericykim](https://github.com/ericykim) for PR #479.
26
+
27
+ ### Changed
28
+ - `/mcp setup` now lets you choose project `.mcp.json` or global `~/.config/mcp/mcp.json` as the write target for new shared MCP servers, while keeping Pi-owned files and compatibility inputs in the advanced flow. The bundled `mcp-scripting` skill is manual-only by default. Thanks to [@w-winter](https://github.com/w-winter) for #477.
29
+
30
+ ### Fixed
31
+ - MCP 2026 multi-round input flows now work more reliably across proxy, direct, resource, and UI-resource calls, with clearer no-UI errors and cancellation cleanup.
32
+ - MCP 2026-07-28 catalog listens now recover from dropped listens, refresh quietly when catalogs change, and notify open UIs when resources update. (#468)
33
+ - MCP App views now load through a separate loopback sandbox proxy origin so storage APIs work without exposing host session capabilities. Thanks to [@drewbitt](https://github.com/drewbitt) for #480.
34
+ - Implicit OAuth now reuses URL-bound stored credentials while preserving anonymous fallback. Thanks to [@wilt00](https://github.com/wilt00) for #471.
35
+ - Per-server proxy lists now distinguish cached lazy tools from servers that need authentication while preserving active failure backoff. Thanks to [@inattendu](https://github.com/inattendu) for PR #474.
36
+
10
37
  ## [2.31.0] - 2026-08-28
11
38
 
12
39
  ### Highlights