* feat(apps): AppsScreen and InspectorView wiring (#1263) Adds the Apps tab as a top-level screen composed of `AppListItem`, `AppDetailPanel`, and `AppRenderer`. The screen is presentational — selection, form values, running, and maximize state are local; data and side effects come in via props and callbacks. InspectorView filters `tools` through `isAppTool` (wrapped in a try/catch so a malformed `_meta.ui.resourceUri` skips one tool instead of dropping every following App), exposes the Apps tab in `ALL_TABS` after Tools, and wires Phase-3 stub callbacks plus a stub bridge factory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(apps): extract AppControls and rename Refresh button Pulls the sidebar list, search input, "List updated" indicator, and Refresh button out of `AppsScreen` into a dedicated `AppControls` group, mirroring `ToolControls` and `TaskControls`. The Refresh button moves from a full-width row beneath the list to the top-right of the title row (single label "Refresh", matching `TaskControls`). `AppsScreen` keeps the right-pane orchestration (selection, form, running, maximize, header actions) and now delegates the entire sidebar to `AppControls`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(stories): seed InspectorView demo tools with MCP Apps Adds three apps (Cohort Data, Weather Widget, Ops Dashboard) carrying real `_meta.ui.resourceUri` to the demo tools list so the Apps tab has content in the Default story alongside the existing tools. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(apps): drop AppDetailPanel title row to avoid duplicate name The parent AppsScreen header already shows the app name, so the panel's own title/icon row rendered the name twice. Remove the title row (and its now-unused imports, styled subcomponents, and the four tests that asserted on title/icon rendering). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(resources): drop annotation badges from ResourceListItem The list row now renders just the resource title/name. Audience and priority badges remain visible in the preview/template panels, so the data is still surfaced — just not duplicated in the picker. Drops the now-unused AnnotationBadge import, the badge-only stories, and the badge-rendering test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(apps): address review — share hasInputFields, show header icon - Extracts the duplicated `Object.keys(inputSchema.properties ?? {}).length > 0` check from `AppsScreen` (and the equivalent in `AppDetailPanel`'s `hasFields`) into a shared `hasInputFields` helper in `toolUtils`, with tests. - Renders the selected tool's first icon next to the right-pane header title in `AppsScreen`, restoring the icon visibility that was lost when `AppDetailPanel`'s title row was removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(apps): finish hasInputFields extraction; document isOpening - AppDetailPanel now actually calls the shared `hasInputFields` helper instead of inlining the same `Object.keys(...).length > 0` check. - AppsScreen comments why `isOpening={false}` is hardcoded: the screen flips to AppRenderer synchronously, so the loading state is unreachable here — the prop remains for standalone use and Phase 3 wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
323f42d11c
commit
5f296589ea
2 changed files with 1210 additions and 164 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { resolveDisplayLabel } from "./toolUtils";
|
||||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { hasInputFields, resolveDisplayLabel } from "./toolUtils";
|
||||
|
||||
describe("resolveDisplayLabel", () => {
|
||||
it("returns the title when provided", () => {
|
||||
|
|
@ -18,3 +19,31 @@ describe("resolveDisplayLabel", () => {
|
|||
expect(resolveDisplayLabel("send_message", "")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasInputFields", () => {
|
||||
const baseTool = (inputSchema: Tool["inputSchema"]): Tool => ({
|
||||
name: "t",
|
||||
inputSchema,
|
||||
});
|
||||
|
||||
it("returns false when properties is missing", () => {
|
||||
expect(hasInputFields(baseTool({ type: "object" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when properties is empty", () => {
|
||||
expect(hasInputFields(baseTool({ type: "object", properties: {} }))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns true when properties has at least one entry", () => {
|
||||
expect(
|
||||
hasInputFields(
|
||||
baseTool({
|
||||
type: "object",
|
||||
properties: { x: { type: "string" } },
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
/**
|
||||
* Returns the display label for an MCP entity that follows the BaseMetadata
|
||||
* shape (Tool, Prompt, Resource): the optional `title` if provided, else the
|
||||
|
|
@ -7,3 +9,13 @@
|
|||
export function resolveDisplayLabel(name: string, title?: string): string {
|
||||
return title ?? name;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the tool's input schema declares at least one property — used by
|
||||
* App-flow callers to decide whether to render a form or auto-launch. Kept in
|
||||
* one place so the definition of "has fields" stays consistent if it ever
|
||||
* grows to consider `additionalProperties`, `anyOf`, etc.
|
||||
*/
|
||||
export function hasInputFields(tool: Tool): boolean {
|
||||
return Object.keys(tool.inputSchema.properties ?? {}).length > 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue