npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@imenam/mcp-gui-interface

v1.0.5

Published

Shared library for MCP GUI lifecycle management — spawning, IPC, proxy registration, logging

Downloads

229

Readme

@imenam/mcp-gui-interface

Shared library for MCP GUI lifecycle management — spawning, IPC, proxy registration, and logging.

This package encapsulates the boilerplate every MCP server needs when it runs a companion GUI: spawning a child process safely, communicating with it over IPC, registering it with the central proxy, and logging without polluting stdout.


Installation

npm install @imenam/mcp-gui-interface

No runtime dependencies. Requires Node.js with ESM support ("type": "module").


Why this library exists

MCP servers communicate with their host (Cursor, Claude Desktop, etc.) over stdout using JSON-RPC. Any console.log or stray output on stdout will corrupt the protocol and break the connection.

When an MCP server also needs to serve a GUI (typically an Express server), it must:

  1. Spawn the GUI as a separate child process so its stdout doesn't pollute the parent's JSON-RPC channel.
  2. Keep the two processes in sync via typed IPC messages.
  3. Register the GUI with a central proxy so it can be reached at a predictable URL.
  4. Log safely — always to stderr and/or a log file, never to stdout.

This library handles all four concerns.


API Reference

GuiLauncher

Spawns and supervises a GUI child process.

import { GuiLauncher } from "@imenam/mcp-gui-interface";

const launcher = new GuiLauncher({
  guiPath: "./dist/gui.js",     // Path to the GUI entry point (forked with Node)
  env: { PORT: "3000" },        // Extra environment variables passed to the child
  maxRestarts: 3,               // Max auto-restarts on crash (default: 3)
  restartDelay: 2000,           // Delay in ms before restarting (default: 2000)
  onMessage: (msg) => {         // Optional callback for incoming IPC messages
    console.error("Received:", msg);
  },
});

launcher.start();

Options (GuiLauncherOptions)

| Property | Type | Default | Description | |---|---|---|---| | guiPath | string | — | Absolute or relative path to the GUI script to fork | | env | Record<string, string> | {} | Additional env vars merged with process.env | | maxRestarts | number | 3 | Maximum number of auto-restarts after unexpected crashes | | restartDelay | number | 2000 | Milliseconds to wait before each restart attempt | | onMessage | (msg: IpcMessage) => void | — | Callback invoked when the GUI sends an IPC message |

Methods

| Method | Returns | Description | |---|---|---| | start() | ChildProcess | Spawns (or re-spawns) the GUI process | | send(message) | boolean | Fire-and-forget IPC message to the GUI | | getIpcHub() | IpcHub | Access the underlying IpcHub for advanced usage | | getProcess() | ChildProcess \| null | The current child process instance | | cleanup() | Promise<void> | Gracefully terminates the GUI (SIGTERM → SIGKILL after 3s) |

The launcher automatically handles SIGINT, SIGTERM, and stdin close events so the GUI is always cleaned up when the MCP server exits.


IpcHub

Typed IPC messaging between the MCP server (parent) and the GUI (child).

import { IpcHub } from "@imenam/mcp-gui-interface";

// Parent side (access via GuiLauncher.getIpcHub())
const ipc = launcher.getIpcHub();

// Fire-and-forget
ipc.send({ type: "CONFIG_UPDATE", data: { theme: "dark" }, timestamp: new Date().toISOString() });

// Request/response with correlation ID and timeout
const response = await ipc.request({ type: "GET_STATUS" }, 3000);
console.error(response.data);

// Subscribe to all incoming messages
ipc.onMessage((msg) => {
  if (msg.type === "READY") console.error("GUI is ready");
});

IpcMessage interface

interface IpcMessage {
  type: string;          // Message type identifier
  correlationId?: string; // Auto-set by request() for matching responses
  data?: any;            // Payload
  error?: string;        // Error description (used in error responses)
  timestamp: string;     // ISO 8601 timestamp
}

Methods

| Method | Description | |---|---| | send(message) | Sends a typed message. Returns false if the process is not connected. | | onMessage(callback) | Subscribes to all incoming messages from the target process. | | request(message, timeout?) | Sends a message and waits for a response with a matching correlationId. Rejects on timeout (default: 2000ms) or if the process is disconnected. |


ProxyClient

Registers and unregisters the GUI with the central HTTP proxy (mcp-http-gateway).

import { ProxyClient } from "@imenam/mcp-gui-interface";

const proxy = new ProxyClient("http://localhost:4242");

// Register — proxy allocates a port and returns it
const result = await proxy.register(
  { path: "/my-app", name: "my-app" },
  fallbackPort  // Used if proxy is unreachable
);

if (result.success) {
  console.error(`GUI available at port ${result.port}`);
} else {
  console.error(`Running on fallback port ${fallbackPort}`);
}

// Check current status
proxy.getStatus(); // "connected" | "fallback" | "error"

// Unregister on shutdown
await proxy.unregister();

RegisterOptions

| Property | Type | Description | |---|---|---| | path | string | The URL path to register (e.g. /my-app) | | name | string | Optional display name for the app | | port | number | Optional preferred port |

RegisterResult

| Property | Type | Description | |---|---|---| | success | boolean | Whether registration succeeded | | port | number | Allocated port (or fallbackPort on failure) | | url | string \| undefined | Full URL returned by the proxy | | error | string \| undefined | Error description on failure |

The proxy registration has a 1-second timeout. If the proxy is unreachable, status is set to "fallback" and the fallback port is used — the GUI still starts normally.


setupLogging / createLogger

Safe logging for MCP servers, keeping stdout clean for JSON-RPC.

import { setupLogging, createLogger } from "@imenam/mcp-gui-interface";

// Call once at startup in the MCP server process
setupLogging({
  processLabel: "MY-MCP",          // Label used in log file entries
  logDir: "./logs",                // Optional. Default: .mcp-gui/logs/
});

// Create scoped loggers anywhere in the codebase
const logger = createLogger("MyModule");

logger.info("Server started");
logger.warn("Config missing, using defaults");
logger.error("Failed to connect", { reason: "timeout" });
logger.debug("Verbose detail");

What setupLogging does:

  • Redirects console.log, console.info, console.warnconsole.error (protecting stdout).
  • Patches console.error to also append every line to a rotating log file at .mcp-gui/logs/server.log.
  • Log entries include ISO timestamp, PID, and process label.

SetupLoggingOptions

| Property | Type | Default | Description | |---|---|---|---| | processLabel | string | — | Label shown in log file lines | | logDir | string | .mcp-gui/logs | Directory where server.log is written |

Log format:

[2026-03-10T12:00:00.000Z] [PID 1234] [MY-MCP] [INFO] [MyModule] Server started

Complete usage example

Here is a typical MCP server that uses all four features:

import { GuiLauncher, ProxyClient, setupLogging, createLogger } from "@imenam/mcp-gui-interface";

// 1. Safe logging (must be called first)
setupLogging({ processLabel: "MY-MCP" });
const logger = createLogger("Main");

// 2. Register the GUI with the proxy
const proxy = new ProxyClient(process.env.PROXY_URL ?? "http://localhost:4242");
const result = await proxy.register({ path: "/my-app", name: "my-app" }, 3001);
const port = result.port;

// 3. Launch the GUI child process
const launcher = new GuiLauncher({
  guiPath: new URL("./gui.js", import.meta.url).pathname,
  env: { PORT: String(port) },
  onMessage: (msg) => {
    if (msg.type === "READY") logger.info("GUI reported ready");
  },
});

launcher.start();
logger.info(`GUI process started on port ${port}`);

// 4. Two-way IPC: request data from the GUI
const ipc = launcher.getIpcHub();
const status = await ipc.request({ type: "GET_STATUS" });
logger.info("GUI status:", status.data);

Type reference

All public types are exported from the package root:

import type {
  GuiLauncherOptions,
  IpcMessage,
  RegisterOptions,
  RegisterResult,
  ProxyConfig,
  Logger,
  SetupLoggingOptions,
} from "@imenam/mcp-gui-interface";

Development

# Build
npm run build

# Watch mode
npm run build:watch

# Release (patch bump, build, publish, push tag)
npm run release

The compiled output and type declarations are in dist/.


License

ISC © Matthieu Pesnot-Pin