Skip to content

Commit b318070

Browse files
committed
Made logging consistent between all clients (using MCP_LOG_FILE), updated client readmes. Made logging initializaiton async so we have logging ready before app runs or does an early exit (this addresses the SonicBoom errors seen on early app termination). Reorganized shared logging code (which is node code) into core/logging/node. Fixed CLI error reporting through launcher (just error message now, no stack trace).
1 parent f53f3bc commit b318070

19 files changed

Lines changed: 164 additions & 56 deletions

clients/cli/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ Options that specify the MCP server (config file, ad-hoc command/URL, env vars,
9898
| `--metadata <key=value>` | General metadata (key=value); applied to all methods. |
9999
| `--tool-metadata <key=value>` | Tool-specific metadata for `tools/call`. |
100100

101+
### Logging (env)
102+
103+
| Env var | Description |
104+
| -------------- | --------------------------------------------------------------- |
105+
| `MCP_LOG_FILE` | If set, CLI and InspectorClient logs are appended to this file. |
106+
101107
## Why use the CLI?
102108

103109
While the Web Client provides a rich visual interface, the CLI is designed for:

clients/cli/src/cli.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import {
1818
parseKeyValuePair as parseEnvPair,
1919
parseHeaderPair,
2020
} from "@modelcontextprotocol/inspector-core/mcp/node/index.js";
21+
import {
22+
createFileLogger,
23+
silentLogger,
24+
} from "@modelcontextprotocol/inspector-core/logging/node";
2125
import type { JsonValue } from "@modelcontextprotocol/inspector-core/mcp/index.js";
2226
import {
2327
LoggingLevelSchema,
@@ -58,9 +62,21 @@ async function callMethod(
5862
const version = packageJson.version;
5963
const clientIdentity = { name, version };
6064

65+
const logger = process.env.MCP_LOG_FILE
66+
? await createFileLogger({
67+
dest: process.env.MCP_LOG_FILE,
68+
append: true,
69+
mkdir: true,
70+
level: "info",
71+
name: "mcp-inspector-cli",
72+
})
73+
: silentLogger;
74+
logger.info("CLI starting");
75+
6176
const inspectorClient = new InspectorClient(serverConfig, {
6277
environment: {
6378
transport: createTransportNode,
79+
logger,
6480
},
6581
clientIdentity,
6682
initialLoggingLevel: "debug",
@@ -413,8 +429,12 @@ function parseArgs(argv?: string[]): {
413429
}
414430

415431
export async function runCli(argv?: string[]): Promise<void> {
416-
const { serverConfig, methodArgs } = parseArgs(argv ?? process.argv);
417-
await callMethod(serverConfig, methodArgs);
432+
try {
433+
const { serverConfig, methodArgs } = parseArgs(argv ?? process.argv);
434+
await callMethod(serverConfig, methodArgs);
435+
} catch (error) {
436+
handleError(error);
437+
}
418438
}
419439

420440
export async function main(): Promise<void> {

clients/tui/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ When connecting to SSE or Streamable HTTP servers that use OAuth, you can pass:
3939
| `--client-metadata-url <url>` | OAuth Client ID Metadata Document URL (CIMD). |
4040
| `--callback-url <url>` | OAuth redirect/callback listener URL (default: `http://127.0.0.1:0/oauth/callback`). |
4141

42+
### Logging (env)
43+
44+
| Env var | Description |
45+
| -------------- | --------------------------------------------------------------- |
46+
| `MCP_LOG_FILE` | If set, TUI and InspectorClient logs are appended to this file. |
47+
4248
## Features
4349

4450
The TUI provides terminal-native tabs and panes for interacting with your MCP server:

clients/tui/src/logger.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1-
import path from "node:path";
2-
import pino from "pino";
1+
import type { Logger } from "pino";
2+
import {
3+
silentLogger,
4+
createFileLogger,
5+
} from "@modelcontextprotocol/inspector-core/logging/node";
36

4-
const logDir =
5-
process.env.MCP_INSPECTOR_LOG_DIR ??
6-
path.join(
7-
process.env.HOME || process.env.USERPROFILE || ".",
8-
".mcp-inspector",
9-
);
10-
const logPath = path.join(logDir, "auth.log");
7+
/**
8+
* TUI logger (InspectorClient events, auth, etc.).
9+
* File logger when MCP_LOG_FILE is set, else silentLogger.
10+
*/
11+
export let tuiLogger: Logger = silentLogger;
1112

1213
/**
13-
* TUI file logger for auth and InspectorClient events.
14-
* Writes to ~/.mcp-inspector/auth.log so TUI console output is not corrupted.
15-
* The app controls logger creation and configuration.
14+
* If MCP_LOG_FILE is set, creates a file logger (awaits destination ready);
15+
* otherwise uses silentLogger. Call at the start of runTui() before any work
16+
* that might call process.exit().
1617
*/
17-
export const tuiLogger = pino(
18-
{
19-
name: "mcp-inspector-tui",
20-
level: process.env.LOG_LEVEL ?? "info",
21-
},
22-
pino.destination({ dest: logPath, append: true, mkdir: true }),
23-
);
18+
export async function initTuiLogger(): Promise<void> {
19+
if (process.env.MCP_LOG_FILE) {
20+
tuiLogger = await createFileLogger({
21+
dest: process.env.MCP_LOG_FILE,
22+
append: true,
23+
mkdir: true,
24+
level: "info",
25+
name: "mcp-inspector-tui",
26+
});
27+
} else {
28+
tuiLogger = silentLogger;
29+
}
30+
}

clients/tui/tui.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import {
99
parseKeyValuePair,
1010
parseHeaderPair,
1111
} from "@modelcontextprotocol/inspector-core/mcp/node/index.js";
12+
import { initTuiLogger, tuiLogger } from "./src/logger.js";
1213
import App from "./src/App.js";
1314

1415
export async function runTui(args?: string[]): Promise<void> {
16+
await initTuiLogger();
17+
tuiLogger.info("TUI starting");
18+
1519
const program = new Command();
1620

1721
program

clients/web/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ All of these are set via **environment variables**; the web app has no command-l
7272
| **Sandbox port** (MCP Apps) | Env: `MCP_SANDBOX_PORT`; if unset, `SERVER_PORT` (legacy). Use `0` or leave unset for an automatic port. | automatic |
7373
| **Storage directory** (e.g. OAuth) | Env: `MCP_STORAGE_DIR` | (unset) |
7474
| **Allowed origins** (CORS) | Env: `ALLOWED_ORIGINS` (comma-separated) | client origin only |
75-
| **Log file** | Env: `MCP_LOG_FILE` — if set, server logs are appended to this file. | (unset) |
75+
| **Log file** | Env: `MCP_LOG_FILE` — if set, server and InspectorClient logs are appended to this file. | (unset) |
7676
| **Open browser on start** | Env: `MCP_AUTO_OPEN_ENABLED` — set to `false` to disable. | `true` |
7777
| **Development mode** | CLI only: `--dev` (Vite with HMR). No env var. | off |
7878

clients/web/src/components/AuthDebugger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "react";
22
import { Button } from "@/components/ui/button";
33
import { AlertCircle } from "lucide-react";
44
import type { InspectorClient } from "@modelcontextprotocol/inspector-core/mcp/index.js";
5-
import { silentLogger } from "@modelcontextprotocol/inspector-core/logging";
5+
import { silentLogger } from "@modelcontextprotocol/inspector-core/logging/node";
66
import type { AuthGuidedState } from "@modelcontextprotocol/inspector-core/auth/types.js";
77
import type { WebEnvironmentResult } from "@/lib/adapters/environmentFactory";
88
import { OAuthFlowProgress } from "./OAuthFlowProgress";

clients/web/src/components/OAuthCallback.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
22
import type { InspectorClient } from "@modelcontextprotocol/inspector-core/mcp/index.js";
33
import type { WebEnvironmentResult } from "@/lib/adapters/environmentFactory";
44
import { parseOAuthState } from "@modelcontextprotocol/inspector-core/auth/index.js";
5-
import { silentLogger } from "@modelcontextprotocol/inspector-core/logging";
5+
import { silentLogger } from "@modelcontextprotocol/inspector-core/logging/node";
66
import useTheme from "@/lib/hooks/useTheme";
77
import { useToast } from "@/lib/hooks/useToast";
88
import {

clients/web/src/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface WebServerHandle {
3434
export async function startHonoServer(
3535
config: WebServerConfig,
3636
): Promise<WebServerHandle> {
37+
config.logger.info("Web server starting");
3738
const sandboxController = createSandboxController({
3839
port: config.sandboxPort,
3940
host: config.sandboxHost,
@@ -131,7 +132,7 @@ export async function startHonoServer(
131132

132133
/** Run when this file is executed as the main module (e.g. node dist/server.js). */
133134
async function runStandalone(): Promise<void> {
134-
const config = buildWebServerConfigFromEnv();
135+
const config = await buildWebServerConfigFromEnv();
135136
const handle = await startHonoServer(config);
136137
const shutdown = () => {
137138
void handle.close().then(() => process.exit(0));

clients/web/src/vite-hono-plugin.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ import {
1515
} from "./web-server-config.js";
1616

1717
/**
18-
* Plugin factory. Caller must pass a WebServerConfig (runner builds it from argv; vite.config builds it from env via buildWebServerConfigFromEnv).
18+
* Plugin factory. Caller must pass a WebServerConfig or Promise<WebServerConfig>
19+
* (runner builds from argv; vite.config passes buildWebServerConfigFromEnv() which is async).
1920
*/
20-
export function honoMiddlewarePlugin(config: WebServerConfig): Plugin {
21+
export function honoMiddlewarePlugin(
22+
config: WebServerConfig | Promise<WebServerConfig>,
23+
): Plugin {
2124
return {
2225
name: "hono-api-middleware",
2326
async configureServer(server) {
27+
const resolvedConfig = await Promise.resolve(config);
28+
resolvedConfig.logger.info("Web server starting (dev)");
2429
const sandboxController = createSandboxController({
25-
port: config.sandboxPort,
26-
host: config.sandboxHost,
30+
port: resolvedConfig.sandboxPort,
31+
host: resolvedConfig.sandboxHost,
2732
});
2833
await sandboxController.start();
2934

@@ -40,13 +45,15 @@ export function honoMiddlewarePlugin(config: WebServerConfig): Plugin {
4045
};
4146

4247
const { app: honoApp, authToken: resolvedToken } = createRemoteApp({
43-
authToken: config.dangerouslyOmitAuth ? undefined : config.authToken,
44-
dangerouslyOmitAuth: config.dangerouslyOmitAuth,
45-
storageDir: config.storageDir,
46-
allowedOrigins: config.allowedOrigins,
48+
authToken: resolvedConfig.dangerouslyOmitAuth
49+
? undefined
50+
: resolvedConfig.authToken,
51+
dangerouslyOmitAuth: resolvedConfig.dangerouslyOmitAuth,
52+
storageDir: resolvedConfig.storageDir,
53+
allowedOrigins: resolvedConfig.allowedOrigins,
4754
sandboxUrl: sandboxController.getUrl() ?? undefined,
48-
logger: config.logger,
49-
initialConfig: webServerConfigToInitialPayload(config),
55+
logger: resolvedConfig.logger,
56+
initialConfig: webServerConfigToInitialPayload(resolvedConfig),
5057
});
5158

5259
const sandboxUrl = sandboxController.getUrl();
@@ -56,16 +63,16 @@ export function honoMiddlewarePlugin(config: WebServerConfig): Plugin {
5663
const actualPort =
5764
typeof address === "object" && address !== null
5865
? address.port
59-
: config.port;
66+
: resolvedConfig.port;
6067

6168
const url = printServerBanner(
62-
config,
69+
resolvedConfig,
6370
actualPort,
6471
resolvedToken,
6572
sandboxUrl ?? undefined,
6673
);
6774

68-
if (config.autoOpen) {
75+
if (resolvedConfig.autoOpen) {
6976
open(url);
7077
}
7178
};

0 commit comments

Comments
 (0)