Merge branch 'main' into perf_useTheme
This commit is contained in:
@@ -108,6 +108,21 @@ const DynamicJsonForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatJson = () => {
|
||||||
|
try {
|
||||||
|
const jsonStr = rawJsonValue.trim();
|
||||||
|
if (!jsonStr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formatted = JSON.stringify(JSON.parse(jsonStr), null, 2);
|
||||||
|
setRawJsonValue(formatted);
|
||||||
|
debouncedUpdateParent(formatted);
|
||||||
|
setJsonError(undefined);
|
||||||
|
} catch (err) {
|
||||||
|
setJsonError(err instanceof Error ? err.message : "Invalid JSON");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderFormFields = (
|
const renderFormFields = (
|
||||||
propSchema: JsonSchemaType,
|
propSchema: JsonSchemaType,
|
||||||
currentValue: JsonValue,
|
currentValue: JsonValue,
|
||||||
@@ -353,7 +368,12 @@ const DynamicJsonForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end space-x-2">
|
||||||
|
{isJsonMode && (
|
||||||
|
<Button variant="outline" size="sm" onClick={formatJson}>
|
||||||
|
Format JSON
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
|
<Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
|
||||||
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { Copy } from "lucide-react";
|
import { Copy } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import JsonView from "./JsonView";
|
||||||
|
|
||||||
const HistoryAndNotifications = ({
|
const HistoryAndNotifications = ({
|
||||||
requestHistory,
|
requestHistory,
|
||||||
@@ -74,9 +75,9 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
<div className="bg-background p-2 rounded">
|
||||||
{JSON.stringify(JSON.parse(request.request), null, 2)}
|
<JsonView data={request.request} />
|
||||||
</pre>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{request.response && (
|
{request.response && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
@@ -91,13 +92,9 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
<div className="bg-background p-2 rounded">
|
||||||
{JSON.stringify(
|
<JsonView data={request.response} />
|
||||||
JSON.parse(request.response),
|
</div>
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -146,9 +143,11 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
<div className="bg-background p-2 rounded">
|
||||||
{JSON.stringify(notification, null, 2)}
|
<JsonView
|
||||||
</pre>
|
data={JSON.stringify(notification, null, 2)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import Editor from "react-simple-code-editor";
|
|||||||
import Prism from "prismjs";
|
import Prism from "prismjs";
|
||||||
import "prismjs/components/prism-json";
|
import "prismjs/components/prism-json";
|
||||||
import "prismjs/themes/prism.css";
|
import "prismjs/themes/prism.css";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
interface JsonEditorProps {
|
interface JsonEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -16,49 +15,25 @@ const JsonEditor = ({
|
|||||||
onChange,
|
onChange,
|
||||||
error: externalError,
|
error: externalError,
|
||||||
}: JsonEditorProps) => {
|
}: JsonEditorProps) => {
|
||||||
const [editorContent, setEditorContent] = useState(value);
|
const [editorContent, setEditorContent] = useState(value || "");
|
||||||
const [internalError, setInternalError] = useState<string | undefined>(
|
const [internalError, setInternalError] = useState<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditorContent(value);
|
setEditorContent(value || "");
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const formatJson = (json: string): string => {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(json), null, 2);
|
|
||||||
} catch {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditorChange = (newContent: string) => {
|
const handleEditorChange = (newContent: string) => {
|
||||||
setEditorContent(newContent);
|
setEditorContent(newContent);
|
||||||
setInternalError(undefined);
|
setInternalError(undefined);
|
||||||
onChange(newContent);
|
onChange(newContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormatJson = () => {
|
|
||||||
try {
|
|
||||||
const formatted = formatJson(editorContent);
|
|
||||||
setEditorContent(formatted);
|
|
||||||
onChange(formatted);
|
|
||||||
setInternalError(undefined);
|
|
||||||
} catch (err) {
|
|
||||||
setInternalError(err instanceof Error ? err.message : "Invalid JSON");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayError = internalError || externalError;
|
const displayError = internalError || externalError;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative space-y-2">
|
<div className="relative">
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" size="sm" onClick={handleFormatJson}>
|
|
||||||
Format JSON
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={`border rounded-md ${
|
className={`border rounded-md ${
|
||||||
displayError
|
displayError
|
||||||
|
|||||||
228
client/src/components/JsonView.tsx
Normal file
228
client/src/components/JsonView.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { useState, memo } from "react";
|
||||||
|
import { JsonValue } from "./DynamicJsonForm";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface JsonViewProps {
|
||||||
|
data: unknown;
|
||||||
|
name?: string;
|
||||||
|
initialExpandDepth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryParseJson(str: string): { success: boolean; data: JsonValue } {
|
||||||
|
const trimmed = str.trim();
|
||||||
|
if (
|
||||||
|
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
||||||
|
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
||||||
|
) {
|
||||||
|
return { success: false, data: str };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return { success: true, data: JSON.parse(str) };
|
||||||
|
} catch {
|
||||||
|
return { success: false, data: str };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonView = memo(
|
||||||
|
({ data, name, initialExpandDepth = 3 }: JsonViewProps) => {
|
||||||
|
const normalizedData =
|
||||||
|
typeof data === "string"
|
||||||
|
? tryParseJson(data).success
|
||||||
|
? tryParseJson(data).data
|
||||||
|
: data
|
||||||
|
: data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="font-mono text-sm transition-all duration-300 ">
|
||||||
|
<JsonNode
|
||||||
|
data={normalizedData as JsonValue}
|
||||||
|
name={name}
|
||||||
|
depth={0}
|
||||||
|
initialExpandDepth={initialExpandDepth}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
JsonView.displayName = "JsonView";
|
||||||
|
|
||||||
|
interface JsonNodeProps {
|
||||||
|
data: JsonValue;
|
||||||
|
name?: string;
|
||||||
|
depth: number;
|
||||||
|
initialExpandDepth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonNode = memo(
|
||||||
|
({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth);
|
||||||
|
|
||||||
|
const getDataType = (value: JsonValue): string => {
|
||||||
|
if (Array.isArray(value)) return "array";
|
||||||
|
if (value === null) return "null";
|
||||||
|
return typeof value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataType = getDataType(data);
|
||||||
|
|
||||||
|
const typeStyleMap: Record<string, string> = {
|
||||||
|
number: "text-blue-600",
|
||||||
|
boolean: "text-amber-600",
|
||||||
|
null: "text-purple-600",
|
||||||
|
undefined: "text-gray-600",
|
||||||
|
string: "text-green-600 break-all whitespace-pre-wrap",
|
||||||
|
default: "text-gray-700",
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCollapsible = (isArray: boolean) => {
|
||||||
|
const items = isArray
|
||||||
|
? (data as JsonValue[])
|
||||||
|
: Object.entries(data as Record<string, JsonValue>);
|
||||||
|
const itemCount = items.length;
|
||||||
|
const isEmpty = itemCount === 0;
|
||||||
|
|
||||||
|
const symbolMap = {
|
||||||
|
open: isArray ? "[" : "{",
|
||||||
|
close: isArray ? "]" : "}",
|
||||||
|
collapsed: isArray ? "[ ... ]" : "{ ... }",
|
||||||
|
empty: isArray ? "[]" : "{}",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
{name && (
|
||||||
|
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="text-gray-500">{symbolMap.empty}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div
|
||||||
|
className="flex items-center mr-1 rounded cursor-pointer group hover:bg-gray-800/10 dark:hover:bg-gray-800/20"
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
|
{name && (
|
||||||
|
<span className="mr-1 text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isExpanded ? (
|
||||||
|
<span className="text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||||
|
{symbolMap.open}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="text-gray-600 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||||
|
{symbolMap.collapsed}
|
||||||
|
</span>
|
||||||
|
<span className="ml-1 text-gray-700 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||||
|
{itemCount} {itemCount === 1 ? "item" : "items"}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isExpanded && (
|
||||||
|
<>
|
||||||
|
<div className="pl-2 ml-4 border-l border-gray-200 dark:border-gray-800">
|
||||||
|
{isArray
|
||||||
|
? (items as JsonValue[]).map((item, index) => (
|
||||||
|
<div key={index} className="my-1">
|
||||||
|
<JsonNode
|
||||||
|
data={item}
|
||||||
|
name={`${index}`}
|
||||||
|
depth={depth + 1}
|
||||||
|
initialExpandDepth={initialExpandDepth}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: (items as [string, JsonValue][]).map(([key, value]) => (
|
||||||
|
<div key={key} className="my-1">
|
||||||
|
<JsonNode
|
||||||
|
data={value}
|
||||||
|
name={key}
|
||||||
|
depth={depth + 1}
|
||||||
|
initialExpandDepth={initialExpandDepth}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">
|
||||||
|
{symbolMap.close}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderString = (value: string) => {
|
||||||
|
const maxLength = 100;
|
||||||
|
const isTooLong = value.length > maxLength;
|
||||||
|
|
||||||
|
if (!isTooLong) {
|
||||||
|
return (
|
||||||
|
<div className="flex mr-1 rounded hover:bg-gray-800/20">
|
||||||
|
{name && (
|
||||||
|
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<pre className={typeStyleMap.string}>"{value}"</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex mr-1 rounded group hover:bg-gray-800/20">
|
||||||
|
{name && (
|
||||||
|
<span className="mr-1 text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<pre
|
||||||
|
className={clsx(
|
||||||
|
typeStyleMap.string,
|
||||||
|
"cursor-pointer group-hover:text-green-500",
|
||||||
|
)}
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
title={isExpanded ? "Click to collapse" : "Click to expand"}
|
||||||
|
>
|
||||||
|
{isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case "object":
|
||||||
|
case "array":
|
||||||
|
return renderCollapsible(dataType === "array");
|
||||||
|
case "string":
|
||||||
|
return renderString(data as string);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div className="flex items-center mr-1 rounded hover:bg-gray-800/20">
|
||||||
|
{name && (
|
||||||
|
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className={typeStyleMap[dataType] || typeStyleMap.default}>
|
||||||
|
{data === null ? "null" : String(data)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
JsonNode.displayName = "JsonNode";
|
||||||
|
|
||||||
|
export default JsonView;
|
||||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Combobox } from "@/components/ui/combobox";
|
import { Combobox } from "@/components/ui/combobox";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { TabsContent } from "@/components/ui/tabs";
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import {
|
import {
|
||||||
ListPromptsResult,
|
ListPromptsResult,
|
||||||
PromptReference,
|
PromptReference,
|
||||||
@@ -13,6 +13,7 @@ import { AlertCircle } from "lucide-react";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
||||||
|
import JsonView from "./JsonView";
|
||||||
|
|
||||||
export type Prompt = {
|
export type Prompt = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -151,11 +152,9 @@ const PromptsTab = ({
|
|||||||
Get Prompt
|
Get Prompt
|
||||||
</Button>
|
</Button>
|
||||||
{promptContent && (
|
{promptContent && (
|
||||||
<Textarea
|
<div className="p-4 border rounded">
|
||||||
value={promptContent}
|
<JsonView data={promptContent} />
|
||||||
readOnly
|
</div>
|
||||||
className="h-64 font-mono"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
|
|||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
||||||
|
import JsonView from "./JsonView";
|
||||||
|
|
||||||
const ResourcesTab = ({
|
const ResourcesTab = ({
|
||||||
resources,
|
resources,
|
||||||
@@ -214,9 +215,9 @@ const ResourcesTab = ({
|
|||||||
<AlertDescription>{error}</AlertDescription>
|
<AlertDescription>{error}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
) : selectedResource ? (
|
) : selectedResource ? (
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100">
|
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100">
|
||||||
{resourceContent}
|
<JsonView data={resourceContent} />
|
||||||
</pre>
|
</div>
|
||||||
) : selectedTemplate ? (
|
) : selectedTemplate ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
CreateMessageRequest,
|
CreateMessageRequest,
|
||||||
CreateMessageResult,
|
CreateMessageResult,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import JsonView from "./JsonView";
|
||||||
|
|
||||||
export type PendingRequest = {
|
export type PendingRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -43,9 +44,9 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
|
|||||||
<h3 className="text-lg font-semibold">Recent Requests</h3>
|
<h3 className="text-lg font-semibold">Recent Requests</h3>
|
||||||
{pendingRequests.map((request) => (
|
{pendingRequests.map((request) => (
|
||||||
<div key={request.id} className="p-4 border rounded-lg space-y-4">
|
<div key={request.id} className="p-4 border rounded-lg space-y-4">
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
|
<div className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
|
||||||
{JSON.stringify(request.request, null, 2)}
|
<JsonView data={JSON.stringify(request.request)} />
|
||||||
</pre>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button onClick={() => handleApprove(request.id)}>Approve</Button>
|
<Button onClick={() => handleApprove(request.id)}>Approve</Button>
|
||||||
<Button variant="outline" onClick={() => onReject(request.id)}>
|
<Button variant="outline" onClick={() => onReject(request.id)}>
|
||||||
|
|||||||
@@ -371,36 +371,37 @@ const Sidebar = ({
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<a
|
<Button variant="ghost" title="Inspector Documentation" asChild>
|
||||||
href="https://modelcontextprotocol.io/docs/tools/inspector"
|
<a
|
||||||
target="_blank"
|
href="https://modelcontextprotocol.io/docs/tools/inspector"
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
<Button variant="ghost" title="Inspector Documentation">
|
|
||||||
<CircleHelp className="w-4 h-4 text-gray-800" />
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://modelcontextprotocol.io/docs/tools/debugging"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Button variant="ghost" title="Debugging Guide">
|
|
||||||
<Bug className="w-4 h-4 text-gray-800" />
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/modelcontextprotocol/inspector"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
title="Report bugs or contribute on GitHub"
|
|
||||||
>
|
>
|
||||||
<Github className="w-4 h-4 text-gray-800" />
|
<CircleHelp className="w-4 h-4 text-foreground" />
|
||||||
</Button>
|
</a>
|
||||||
</a>
|
</Button>
|
||||||
|
<Button variant="ghost" title="Debugging Guide" asChild>
|
||||||
|
<a
|
||||||
|
href="https://modelcontextprotocol.io/docs/tools/debugging"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Bug className="w-4 h-4 text-foreground" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
title="Report bugs or contribute on GitHub"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://github.com/modelcontextprotocol/inspector"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Github className="w-4 h-4 text-foreground" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { AlertCircle, Send } from "lucide-react";
|
import { AlertCircle, Send } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
import { escapeUnicode } from "@/utils/escapeUnicode";
|
import JsonView from "./JsonView";
|
||||||
|
|
||||||
const ToolsTab = ({
|
const ToolsTab = ({
|
||||||
tools,
|
tools,
|
||||||
@@ -53,17 +53,14 @@ const ToolsTab = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h4 className="font-semibold mb-2">Invalid Tool Result:</h4>
|
<h4 className="font-semibold mb-2">Invalid Tool Result:</h4>
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
<div className="p-4 border rounded">
|
||||||
{escapeUnicode(toolResult)}
|
<JsonView data={toolResult} />
|
||||||
</pre>
|
</div>
|
||||||
<h4 className="font-semibold mb-2">Errors:</h4>
|
<h4 className="font-semibold mb-2">Errors:</h4>
|
||||||
{parsedResult.error.errors.map((error, idx) => (
|
{parsedResult.error.errors.map((error, idx) => (
|
||||||
<pre
|
<div key={idx} className="p-4 border rounded">
|
||||||
key={idx}
|
<JsonView data={error} />
|
||||||
className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64"
|
</div>
|
||||||
>
|
|
||||||
{escapeUnicode(error)}
|
|
||||||
</pre>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -79,9 +76,9 @@ const ToolsTab = ({
|
|||||||
{structuredResult.content.map((item, index) => (
|
{structuredResult.content.map((item, index) => (
|
||||||
<div key={index} className="mb-2">
|
<div key={index} className="mb-2">
|
||||||
{item.type === "text" && (
|
{item.type === "text" && (
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
<div className="p-4 border rounded">
|
||||||
{item.text}
|
<JsonView data={item.text} />
|
||||||
</pre>
|
</div>
|
||||||
)}
|
)}
|
||||||
{item.type === "image" && (
|
{item.type === "image" && (
|
||||||
<img
|
<img
|
||||||
@@ -100,9 +97,9 @@ const ToolsTab = ({
|
|||||||
<p>Your browser does not support audio playback</p>
|
<p>Your browser does not support audio playback</p>
|
||||||
</audio>
|
</audio>
|
||||||
) : (
|
) : (
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 whitespace-pre-wrap break-words p-4 rounded text-sm overflow-auto max-h-64">
|
<div className="p-4 border rounded">
|
||||||
{escapeUnicode(item.resource)}
|
<JsonView data={item.resource} />
|
||||||
</pre>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -112,9 +109,9 @@ const ToolsTab = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
|
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
<div className="p-4 border rounded">
|
||||||
{escapeUnicode(toolResult.toolResult)}
|
<JsonView data={toolResult.toolResult} />
|
||||||
</pre>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ import {
|
|||||||
McpError,
|
McpError,
|
||||||
CompleteResultSchema,
|
CompleteResultSchema,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
|
CancelledNotificationSchema,
|
||||||
|
ResourceListChangedNotificationSchema,
|
||||||
|
ToolListChangedNotificationSchema,
|
||||||
|
PromptListChangedNotificationSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -250,20 +254,24 @@ export function useConnection({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (onNotification) {
|
if (onNotification) {
|
||||||
client.setNotificationHandler(
|
[
|
||||||
|
CancelledNotificationSchema,
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
onNotification,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.setNotificationHandler(
|
|
||||||
ResourceUpdatedNotificationSchema,
|
|
||||||
onNotification,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.setNotificationHandler(
|
|
||||||
LoggingMessageNotificationSchema,
|
LoggingMessageNotificationSchema,
|
||||||
onNotification,
|
ResourceUpdatedNotificationSchema,
|
||||||
);
|
ResourceListChangedNotificationSchema,
|
||||||
|
ToolListChangedNotificationSchema,
|
||||||
|
PromptListChangedNotificationSchema,
|
||||||
|
].forEach((notificationSchema) => {
|
||||||
|
client.setNotificationHandler(notificationSchema, onNotification);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.fallbackNotificationHandler = (
|
||||||
|
notification: Notification,
|
||||||
|
): Promise<void> => {
|
||||||
|
onNotification(notification);
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onStdErrNotification) {
|
if (onStdErrNotification) {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({
|
|||||||
|
|
||||||
export const NotificationSchema = ClientNotificationSchema.or(
|
export const NotificationSchema = ClientNotificationSchema.or(
|
||||||
StdErrNotificationSchema,
|
StdErrNotificationSchema,
|
||||||
).or(ServerNotificationSchema);
|
)
|
||||||
|
.or(ServerNotificationSchema)
|
||||||
|
.or(BaseNotificationSchema);
|
||||||
|
|
||||||
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
||||||
export type Notification = z.infer<typeof NotificationSchema>;
|
export type Notification = z.infer<typeof NotificationSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user