Phase 2: Refactor Resources cluster to MCP SDK types
Replace ad hoc local types with MCP SDK schema types across the Resources group components and ResourcesScreen, per the v2 UX interfaces plan. - ResourceListItem: accept `resource: Resource | ResourceTemplate` instead of flat `name`/`uri`; render `title ?? name`; delete local `ResourceAnnotations` type - ResourceControls: accept `Resource[]`, `ResourceTemplate[]`, `InspectorResourceSubscription[]` + `selectedUri`/`selectedTemplate` instead of `ResourceItem[]`/`TemplateListItem[]`/`SubscriptionItem[]`; no-op on re-selecting - ResourcePreviewPanel: accept `resource: Resource` + `contents: (TextResourceContents | BlobResourceContents)[]` instead of flat `content: string`; use SDK `Annotations` (audience as Role[]); `lastUpdated` as `Date` not `string` - ResourceTemplatePanel: accept `template: ResourceTemplate` instead of flat props; use SDK `Annotations` - ResourceSubscribedItem: accept `subscription: InspectorResourceSubscription` instead of flat `name`/`lastUpdated`; `lastUpdated` as `Date` - ResourcesScreen: accept `Resource[]`, `ResourceTemplate[]`, `InspectorResourceSubscription[]` + `selectedResourceUri`/ `selectedTemplateUri` + `ReadResourceState`; derive selections via `find()`; add loading/error states - Delete local `ResourceItem`, `TemplateListItem`, `SubscriptionItem`, `SelectedResource`, `SelectedTemplate`, `ResourceAnnotations` types - Update all stories with real SDK fixtures - Update ConnectedView ResourcesActive story Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
61230d533c
commit
d817c46911
2 changed files with 465 additions and 616 deletions
|
|
@ -17,57 +17,96 @@ type Story = StoryObj<typeof ResourcePreviewPanel>;
|
|||
|
||||
export const JsonResource: Story = {
|
||||
args: {
|
||||
uri: "file:///config.json",
|
||||
mimeType: "application/json",
|
||||
content: JSON.stringify(
|
||||
{ name: "mcp-inspector", version: "2.0.0", debug: true },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
resource: {
|
||||
name: "config.json",
|
||||
uri: "file:///config.json",
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///config.json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{ name: "mcp-inspector", version: "2.0.0", debug: true },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
isSubscribed: false,
|
||||
lastUpdated: "2026-03-17T10:30:00Z",
|
||||
lastUpdated: new Date("2026-03-17T10:30:00Z"),
|
||||
},
|
||||
};
|
||||
|
||||
export const TextResource: Story = {
|
||||
args: {
|
||||
uri: "file:///readme.txt",
|
||||
mimeType: "text/plain",
|
||||
content:
|
||||
"This is a plain text resource with some example content.\nLine two of the content.",
|
||||
resource: {
|
||||
name: "readme.txt",
|
||||
uri: "file:///readme.txt",
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///readme.txt",
|
||||
mimeType: "text/plain",
|
||||
text: "This is a plain text resource with some example content.\nLine two of the content.",
|
||||
},
|
||||
],
|
||||
isSubscribed: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Subscribed: Story = {
|
||||
args: {
|
||||
uri: "file:///data.json",
|
||||
mimeType: "application/json",
|
||||
content: JSON.stringify({ status: "active" }, null, 2),
|
||||
resource: {
|
||||
name: "data.json",
|
||||
uri: "file:///data.json",
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///data.json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({ status: "active" }, null, 2),
|
||||
},
|
||||
],
|
||||
isSubscribed: true,
|
||||
lastUpdated: "2026-03-17T12:00:00Z",
|
||||
lastUpdated: new Date("2026-03-17T12:00:00Z"),
|
||||
},
|
||||
};
|
||||
|
||||
export const NotSubscribed: Story = {
|
||||
args: {
|
||||
uri: "file:///data.json",
|
||||
mimeType: "application/json",
|
||||
content: JSON.stringify({ status: "active" }, null, 2),
|
||||
resource: {
|
||||
name: "data.json",
|
||||
uri: "file:///data.json",
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///data.json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({ status: "active" }, null, 2),
|
||||
},
|
||||
],
|
||||
isSubscribed: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithAnnotations: Story = {
|
||||
args: {
|
||||
uri: "file:///settings.json",
|
||||
mimeType: "application/json",
|
||||
content: JSON.stringify({ theme: "dark", lang: "en" }, null, 2),
|
||||
isSubscribed: false,
|
||||
annotations: {
|
||||
audience: "application",
|
||||
priority: 0.8,
|
||||
resource: {
|
||||
name: "settings.json",
|
||||
uri: "file:///settings.json",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
priority: 0.8,
|
||||
},
|
||||
},
|
||||
lastUpdated: "2026-03-17T09:00:00Z",
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///settings.json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({ theme: "dark", lang: "en" }, null, 2),
|
||||
},
|
||||
],
|
||||
isSubscribed: false,
|
||||
lastUpdated: new Date("2026-03-17T09:00:00Z"),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,31 +1,40 @@
|
|||
import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import type { ContentBlock } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
BlobResourceContents,
|
||||
ContentBlock,
|
||||
Resource,
|
||||
TextResourceContents,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AnnotationBadge } from "../../elements/AnnotationBadge/AnnotationBadge";
|
||||
import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
|
||||
import { CopyButton } from "../../elements/CopyButton/CopyButton";
|
||||
import { SubscribeButton } from "../../elements/SubscribeButton/SubscribeButton";
|
||||
|
||||
export interface ResourcePreviewPanelProps {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
annotations?: { audience?: string; priority?: number };
|
||||
content: string;
|
||||
lastUpdated?: string;
|
||||
resource: Resource;
|
||||
contents: (TextResourceContents | BlobResourceContents)[];
|
||||
lastUpdated?: Date;
|
||||
isSubscribed: boolean;
|
||||
onRefresh: () => void;
|
||||
onSubscribe: () => void;
|
||||
onUnsubscribe: () => void;
|
||||
}
|
||||
|
||||
function toContentBlock(content: string, mimeType: string): ContentBlock {
|
||||
if (mimeType.startsWith("image/")) {
|
||||
return { type: "image", data: content, mimeType };
|
||||
function toContentBlock(
|
||||
item: TextResourceContents | BlobResourceContents,
|
||||
): ContentBlock {
|
||||
if ("text" in item) {
|
||||
return { type: "text", text: item.text };
|
||||
}
|
||||
return { type: "text", text: content };
|
||||
const mimeType = item.mimeType ?? "application/octet-stream";
|
||||
if (mimeType.startsWith("image/")) {
|
||||
return { type: "image", data: item.blob, mimeType };
|
||||
}
|
||||
return { type: "text", text: item.blob };
|
||||
}
|
||||
|
||||
function formatLastUpdated(lastUpdated: string): string {
|
||||
return `Last updated: ${lastUpdated}`;
|
||||
function formatLastUpdated(date: Date): string {
|
||||
return `Last updated: ${date.toLocaleString()}`;
|
||||
}
|
||||
|
||||
const HeaderRow = Group.withProps({
|
||||
|
|
@ -74,16 +83,18 @@ const ActionGroup = Group.withProps({
|
|||
const Spacer = Flex.withProps({});
|
||||
|
||||
export function ResourcePreviewPanel({
|
||||
uri,
|
||||
mimeType,
|
||||
annotations,
|
||||
content,
|
||||
resource,
|
||||
contents,
|
||||
lastUpdated,
|
||||
isSubscribed,
|
||||
onRefresh,
|
||||
onSubscribe,
|
||||
onUnsubscribe,
|
||||
}: ResourcePreviewPanelProps) {
|
||||
const { uri, annotations } = resource;
|
||||
const mimeType =
|
||||
contents[0]?.mimeType ?? resource.mimeType ?? "application/octet-stream";
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<HeaderRow>
|
||||
|
|
@ -93,7 +104,9 @@ export function ResourcePreviewPanel({
|
|||
<CopyButton value={uri} />
|
||||
</UriGroup>
|
||||
</HeaderRow>
|
||||
<ContentViewer block={toContentBlock(content, mimeType)} copyable />
|
||||
{contents.map((item, index) => (
|
||||
<ContentViewer key={index} block={toContentBlock(item)} copyable />
|
||||
))}
|
||||
<MetaRow>
|
||||
{lastUpdated ? (
|
||||
<TimestampText>{formatLastUpdated(lastUpdated)}</TimestampText>
|
||||
|
|
@ -105,12 +118,7 @@ export function ResourcePreviewPanel({
|
|||
<FooterRow>
|
||||
<AnnotationGroup>
|
||||
{annotations?.audience && (
|
||||
<AnnotationBadge
|
||||
facet="audience"
|
||||
value={
|
||||
annotations.audience.split(", ") as ("user" | "assistant")[]
|
||||
}
|
||||
/>
|
||||
<AnnotationBadge facet="audience" value={annotations.audience} />
|
||||
)}
|
||||
{annotations?.priority !== undefined && (
|
||||
<AnnotationBadge facet="priority" value={annotations.priority} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue