* 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:
parent
8a5a4e5b6a
commit
9dbd5cc85b
5 changed files with 49 additions and 77 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue