* Remove handling of out-of-spec 'notification/stderr' messages. It's not a thing. See https://github.com/modelcontextprotocol/servers/pull/2469

* Inspect the stderr output of STDIO servers and attempt to assign an appropriate RFC 5424 Syslog Protocol level before sending a leveled logging message to the client
This commit is contained in:
cliffhall 2025-08-21 18:01:29 -04:00
commit 9dbd5cc85b
5 changed files with 49 additions and 77 deletions

View file

@ -34,7 +34,6 @@ import {
useDraggablePane, useDraggablePane,
useDraggableSidebar, useDraggableSidebar,
} from "./lib/hooks/useDraggablePane"; } from "./lib/hooks/useDraggablePane";
import { StdErrNotification } from "./lib/notificationTypes";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -106,9 +105,6 @@ const App = () => {
>(getInitialTransportType); >(getInitialTransportType);
const [logLevel, setLogLevel] = useState<LoggingLevel>("debug"); const [logLevel, setLogLevel] = useState<LoggingLevel>("debug");
const [notifications, setNotifications] = useState<ServerNotification[]>([]); const [notifications, setNotifications] = useState<ServerNotification[]>([]);
const [stdErrNotifications, setStdErrNotifications] = useState<
StdErrNotification[]
>([]);
const [roots, setRoots] = useState<Root[]>([]); const [roots, setRoots] = useState<Root[]>([]);
const [env, setEnv] = useState<Record<string, string>>({}); const [env, setEnv] = useState<Record<string, string>>({});
@ -224,12 +220,6 @@ const App = () => {
onNotification: (notification) => { onNotification: (notification) => {
setNotifications((prev) => [...prev, notification as ServerNotification]); setNotifications((prev) => [...prev, notification as ServerNotification]);
}, },
onStdErrNotification: (notification) => {
setStdErrNotifications((prev) => [
...prev,
notification as StdErrNotification,
]);
},
onPendingRequest: (request, resolve, reject) => { onPendingRequest: (request, resolve, reject) => {
setPendingSampleRequests((prev) => [ setPendingSampleRequests((prev) => [
...prev, ...prev,
@ -757,10 +747,6 @@ const App = () => {
setLogLevel(level); setLogLevel(level);
}; };
const clearStdErrNotifications = () => {
setStdErrNotifications([]);
};
const AuthDebuggerWrapper = () => ( const AuthDebuggerWrapper = () => (
<TabsContent value="auth"> <TabsContent value="auth">
<AuthDebugger <AuthDebugger
@ -829,11 +815,9 @@ const App = () => {
setOauthScope={setOauthScope} setOauthScope={setOauthScope}
onConnect={connectMcpServer} onConnect={connectMcpServer}
onDisconnect={disconnectMcpServer} onDisconnect={disconnectMcpServer}
stdErrNotifications={stdErrNotifications}
logLevel={logLevel} logLevel={logLevel}
sendLogLevelRequest={sendLogLevelRequest} sendLogLevelRequest={sendLogLevelRequest}
loggingSupported={!!serverCapabilities?.logging || false} loggingSupported={!!serverCapabilities?.logging || false}
clearStdErrNotifications={clearStdErrNotifications}
/> />
<div <div
onMouseDown={handleSidebarDragStart} onMouseDown={handleSidebarDragStart}

View file

@ -24,7 +24,6 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { StdErrNotification } from "@/lib/notificationTypes";
import { import {
LoggingLevel, LoggingLevel,
LoggingLevelSchema, LoggingLevelSchema,
@ -62,8 +61,6 @@ interface SidebarProps {
setOauthScope: (scope: string) => void; setOauthScope: (scope: string) => void;
onConnect: () => void; onConnect: () => void;
onDisconnect: () => void; onDisconnect: () => void;
stdErrNotifications: StdErrNotification[];
clearStdErrNotifications: () => void;
logLevel: LoggingLevel; logLevel: LoggingLevel;
sendLogLevelRequest: (level: LoggingLevel) => void; sendLogLevelRequest: (level: LoggingLevel) => void;
loggingSupported: boolean; loggingSupported: boolean;
@ -93,8 +90,6 @@ const Sidebar = ({
setOauthScope, setOauthScope,
onConnect, onConnect,
onDisconnect, onDisconnect,
stdErrNotifications,
clearStdErrNotifications,
logLevel, logLevel,
sendLogLevelRequest, sendLogLevelRequest,
loggingSupported, loggingSupported,
@ -760,36 +755,6 @@ const Sidebar = ({
</Select> </Select>
</div> </div>
)} )}
{stdErrNotifications.length > 0 && (
<>
<div className="mt-4 border-t border-gray-200 pt-4">
<div className="flex justify-between items-center">
<h3 className="text-sm font-medium">
Error output from MCP server
</h3>
<Button
variant="outline"
size="sm"
onClick={clearStdErrNotifications}
className="h-8 px-2"
>
Clear
</Button>
</div>
<div className="mt-2 max-h-80 overflow-y-auto">
{stdErrNotifications.map((notification, index) => (
<div
key={index}
className="text-sm text-red-500 font-mono py-2 border-b border-gray-200 last:border-b-0"
>
{notification.params.content}
</div>
))}
</div>
</div>
</>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -36,7 +36,7 @@ import { useEffect, useState } from "react";
import { useToast } from "@/lib/hooks/useToast"; import { useToast } from "@/lib/hooks/useToast";
import { z } from "zod"; import { z } from "zod";
import { ConnectionStatus } from "../constants"; import { ConnectionStatus } from "../constants";
import { Notification, StdErrNotificationSchema } from "../notificationTypes"; import { Notification } from "../notificationTypes";
import { import {
auth, auth,
discoverOAuthProtectedResourceMetadata, discoverOAuthProtectedResourceMetadata,
@ -92,7 +92,6 @@ export function useConnection({
oauthScope, oauthScope,
config, config,
onNotification, onNotification,
onStdErrNotification,
onPendingRequest, onPendingRequest,
onElicitationRequest, onElicitationRequest,
getRoots, getRoots,
@ -505,13 +504,6 @@ export function useConnection({
}; };
} }
if (onStdErrNotification) {
client.setNotificationHandler(
StdErrNotificationSchema,
onStdErrNotification,
);
}
let capabilities; let capabilities;
try { try {
const transport = const transport =

View file

@ -5,18 +5,8 @@ import {
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod"; import { z } from "zod";
export const StdErrNotificationSchema = BaseNotificationSchema.extend({
method: z.literal("notifications/stderr"),
params: z.object({
content: z.string(),
}),
});
export const NotificationSchema = ClientNotificationSchema.or( export const NotificationSchema = ClientNotificationSchema.or(
StdErrNotificationSchema, ServerNotificationSchema,
) ).or(BaseNotificationSchema);
.or(ServerNotificationSchema)
.or(BaseNotificationSchema);
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
export type Notification = z.infer<typeof NotificationSchema>; export type Notification = z.infer<typeof NotificationSchema>;

View file

@ -401,24 +401,65 @@ app.get(
(serverTransport as StdioClientTransport).stderr!.on("data", (chunk) => { (serverTransport as StdioClientTransport).stderr!.on("data", (chunk) => {
if (chunk.toString().includes("MODULE_NOT_FOUND")) { if (chunk.toString().includes("MODULE_NOT_FOUND")) {
// Server command not found, remove transports
const message = "Command not found, transports removed";
webAppTransport.send({ webAppTransport.send({
jsonrpc: "2.0", jsonrpc: "2.0",
method: "notifications/stderr", method: "notifications/message",
params: { params: {
content: "Command not found, transports removed", level: "alert",
data: {
error: message,
},
}, },
}); });
webAppTransport.close(); webAppTransport.close();
serverTransport.close(); serverTransport.close();
webAppTransports.delete(webAppTransport.sessionId); webAppTransports.delete(webAppTransport.sessionId);
serverTransports.delete(webAppTransport.sessionId); serverTransports.delete(webAppTransport.sessionId);
console.error("Command not found, transports removed"); console.error(message);
} else { } else {
// Inspect message and attempt to assign a RFC 5424 Syslog Protocol level
let level;
let message = chunk.toString();
let ucMsg = chunk.toString().toUpperCase();
if (ucMsg.includes("DEBUG")) {
level = "debug";
} else if (ucMsg.includes("INFO")) {
level = "info";
} else if (ucMsg.includes("NOTICE")) {
level = "notice";
} else if (ucMsg.includes("WARN")) {
level = "warning";
} else if (ucMsg.includes("ERROR")) {
level = "error";
} else if (ucMsg.includes("CRITICAL")) {
level = "critical";
} else if (ucMsg.includes("ALERT")) {
level = "alert";
} else if (ucMsg.includes("EMERGENCY")) {
level = "emergency";
} else if (ucMsg.includes("SIGINT")) {
level = "alert";
message = "SIGINT received. Server shutdown.";
} else if (ucMsg.includes("SIGHUP")) {
level = "alert";
message = "SIGHUP received. Server shutdown.";
} else if (ucMsg.includes("SIGTERM")) {
level = "alert";
message = "SIGTERM received. Server shutdown.";
} else {
level = "info";
}
webAppTransport.send({ webAppTransport.send({
jsonrpc: "2.0", jsonrpc: "2.0",
method: "notifications/stderr", method: "notifications/message",
params: { params: {
content: chunk.toString(), level,
logger: "stdio",
data: {
error: message,
},
}, },
}); });
} }