Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions mcpjam-inspector/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ import {
import { getEffectiveWorkspaceClientCapabilities } from "./lib/client-config";
import { buildEvalsHash } from "./lib/evals-router";
import { withTestingSurface } from "./lib/testing-surface";
import { useClientConfigStore } from "./stores/client-config-store";
import { useWorkspaceClientConfigSyncPending } from "./hooks/use-workspace-client-config-sync-pending";
import { ingestOAuthTraceLogs } from "./stores/traffic-log-store";
import { clearGuestSession } from "./lib/guest-session";

Expand Down Expand Up @@ -663,6 +663,7 @@ export default function App() {
handleLeaveWorkspace,
handleUpdateWorkspace,
handleUpdateClientConfig,
handleUpdateHostContext,
handleDeleteWorkspace,
handleWorkspaceShared,
saveServerConfigWithoutConnecting,
Expand Down Expand Up @@ -852,11 +853,8 @@ export default function App() {

// Get the Convex workspace ID from the active workspace
const activeWorkspace = workspaces[activeWorkspaceId];
const isClientConfigSyncPending = useClientConfigStore(
(state) =>
state.isAwaitingRemoteEcho &&
state.pendingWorkspaceId === activeWorkspaceId,
);
const isClientConfigSyncPending =
useWorkspaceClientConfigSyncPending(activeWorkspaceId);
const hostedClientCapabilities = getEffectiveWorkspaceClientCapabilities(
activeWorkspace?.clientConfig,
) as Record<string, unknown>;
Expand Down Expand Up @@ -1837,7 +1835,11 @@ export default function App() {
)
) : null)}
{activeTab === "views" && (
<ViewsTab selectedServer={appState.selectedServer} />
<ViewsTab
selectedServer={appState.selectedServer}
activeWorkspaceId={activeWorkspaceId}
onSaveHostContext={handleUpdateHostContext}
/>
)}
{activeTab === "conformance" && (
<ConformanceTab server={selectedServerEntry ?? null} />
Expand Down Expand Up @@ -1975,10 +1977,12 @@ export default function App() {
serverConfig={selectedMCPConfig}
serverName={appState.selectedServer}
servers={workspaceServers}
activeWorkspaceId={activeWorkspaceId}
isAuthenticated={isAuthenticated}
isAuthLoading={isAuthLoading}
isServerSyncing={isSelectedServerSyncing}
onConnect={handleConnect}
onSaveHostContext={handleUpdateHostContext}
ensureServersReady={ensureServersReady}
onOnboardingChange={setAppBuilderOnboarding}
playgroundServerSelectorProps={playgroundServerSelectorProps}
Expand Down
7 changes: 3 additions & 4 deletions mcpjam-inspector/client/src/components/ServersTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
DialogDescription,
DialogTitle,
} from "@mcpjam/design-system/dialog";
import type { WorkspaceClientConfig } from "@/lib/client-config";
import type { WorkspaceConnectionConfigDraft } from "@/lib/client-config";
import {
ServerDetailModal,
type ServerDetailTab,
Expand Down Expand Up @@ -513,7 +513,7 @@ interface ServersTabProps {
onNavigateToRegistry?: () => void;
onSaveClientConfig?: (
workspaceId: string,
clientConfig: WorkspaceClientConfig | undefined
clientConfig: WorkspaceConnectionConfigDraft | undefined
) => Promise<void>;
}

Expand Down Expand Up @@ -1551,8 +1551,7 @@ export function ServersTab({
<DialogContent className="flex h-[88vh] w-[min(96vw,88rem)] max-w-[88rem] flex-col gap-0 overflow-hidden p-0 sm:max-w-[88rem]">
<DialogTitle className="sr-only">Connection Settings</DialogTitle>
<DialogDescription className="sr-only">
Edit workspace connection settings, client capabilities, and host
context.
Edit workspace connection settings and client capabilities.
</DialogDescription>
<div className="min-h-0 flex-1 overflow-hidden">
<ClientConfigTab
Expand Down
14 changes: 13 additions & 1 deletion mcpjam-inspector/client/src/components/ViewsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useSharedAppState } from "@/state/app-state-context";
import { ViewsListSidebar } from "./views/ViewsListSidebar";
import { ViewEditorPanel } from "./views/ViewEditorPanel";
import { executeToolApi } from "@/lib/apis/mcp-tools-api";
import type { WorkspaceHostContextDraft } from "@/lib/client-config";
import {
useCurrentDisplayContext,
areDisplayContextsEqual,
Expand All @@ -32,6 +33,11 @@ import { ToolRenderOverride } from "@/components/chat-v2/thread/tool-render-over
import { buildPersistedExecutionReplay } from "@/components/chat-v2/thread/persisted-execution-replay";

interface ViewsTabProps {
activeWorkspaceId?: string | null;
onSaveHostContext?: (
workspaceId: string,
hostContext: WorkspaceHostContextDraft,
) => Promise<void>;
selectedServer?: string;
}

Expand All @@ -49,7 +55,11 @@ function safeSerializeForCompare(value: unknown): string {
}
}

export function ViewsTab({ selectedServer }: ViewsTabProps) {
export function ViewsTab({
activeWorkspaceId = null,
onSaveHostContext,
selectedServer,
}: ViewsTabProps) {
const { isAuthenticated, isLoading } = useConvexAuth();
const posthog = usePostHog();
const appState = useSharedAppState();
Expand Down Expand Up @@ -1116,8 +1126,10 @@ export function ViewsTab({ selectedServer }: ViewsTabProps) {
</div>
) : (
<PlaygroundMain
activeWorkspaceId={activeWorkspaceId}
key={selectedView._id}
serverName={serversById.get(selectedView.serverId) || ""}
onSaveHostContext={onSaveHostContext}
pendingExecution={pendingExecution}
onExecutionInjected={handleExecutionInjected}
isExecuting={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ vi.mock("@/stores/ui-playground-store", () => ({
selector(mockPlaygroundStoreState),
}));

vi.mock("@/stores/client-config-store", () => ({
useClientConfigStore: (
selector: (state: { draftConfig?: { hostContext?: Record<string, unknown> } }) => unknown,
) => selector({ draftConfig: undefined }),
vi.mock("@/stores/host-context-store", () => ({
useHostContextStore: (
selector: (state: { draftHostContext: Record<string, unknown> }) => unknown,
) => selector({ draftHostContext: {} }),
}));

vi.mock("@/stores/traffic-log-store", () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
loadLocalChatGptWidget,
type WidgetCspData,
} from "./chatgpt-widget-loaders";
import { useClientConfigStore } from "@/stores/client-config-store";
import { useHostContextStore } from "@/stores/host-context-store";
import {
extractHostDeviceCapabilities,
extractHostLocale,
Expand Down Expand Up @@ -626,9 +626,7 @@ export function ChatGPTAppRenderer({
const rootRef = useRef<HTMLDivElement>(null);
const inlineWidthRef = useRef<number | undefined>(undefined);
const themeMode = usePreferencesStore((s) => s.themeMode);
const draftHostContext = useClientConfigStore(
(s) => s.draftConfig?.hostContext,
);
const draftHostContext = useHostContextStore((s) => s.draftHostContext);
// Get locale and time zone from playground store, fallback to browser settings
const playgroundLocale = useUIPlaygroundStore((s) => s.globals.locale);
const playgroundTimeZone = useUIPlaygroundStore((s) => s.globals.timeZone);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,8 @@ const {
};
});

const mockClientConfigStoreState = {
draftConfig: undefined as
| {
version: 1;
clientCapabilities: Record<string, unknown>;
hostContext: Record<string, unknown>;
}
| undefined,
const mockHostContextStoreState = {
draftHostContext: {} as Record<string, unknown>,
};

const mockPreferencesState = {
Expand Down Expand Up @@ -165,8 +159,8 @@ vi.mock("@/stores/ui-playground-store", () => ({
selector(mockPlaygroundStoreState),
}));

vi.mock("@/stores/client-config-store", () => ({
useClientConfigStore: (selector: any) => selector(mockClientConfigStoreState),
vi.mock("@/stores/host-context-store", () => ({
useHostContextStore: (selector: any) => selector(mockHostContextStoreState),
}));

vi.mock("@/stores/traffic-log-store", () => ({
Expand Down Expand Up @@ -230,7 +224,7 @@ const baseProps = {
describe("MCPAppsRenderer tool input streaming", () => {
beforeEach(() => {
vi.clearAllMocks();
mockClientConfigStoreState.draftConfig = undefined;
mockHostContextStoreState.draftHostContext = {};
Object.assign(mockPlaygroundStoreState, {
isPlaygroundActive: false,
mcpAppsCspMode: "permissive",
Expand Down Expand Up @@ -341,15 +335,11 @@ describe("MCPAppsRenderer tool input streaming", () => {
});

it("clamps configured host display modes before sending host context", async () => {
mockClientConfigStoreState.draftConfig = {
version: 1,
clientCapabilities: {},
hostContext: {
displayMode: "fullscreen",
availableDisplayModes: ["inline"],
locale: "fr-FR",
timeZone: "Europe/Paris",
},
mockHostContextStoreState.draftHostContext = {
displayMode: "fullscreen",
availableDisplayModes: ["inline"],
locale: "fr-FR",
timeZone: "Europe/Paris",
};

render(<MCPAppsRenderer {...baseProps} />);
Expand All @@ -376,16 +366,12 @@ describe("MCPAppsRenderer tool input streaming", () => {
});

it("filters non-standard host style variables out of the initialize payload", async () => {
mockClientConfigStoreState.draftConfig = {
version: 1,
clientCapabilities: {},
hostContext: {
styles: {
variables: {
"--font-sans": "Custom Sans",
"--mcpjam-theme-preset": "soft-pop",
"--totally-unknown": "ignore-me",
},
mockHostContextStoreState.draftHostContext = {
styles: {
variables: {
"--font-sans": "Custom Sans",
"--mcpjam-theme-preset": "soft-pop",
"--totally-unknown": "ignore-me",
},
},
};
Expand Down Expand Up @@ -488,16 +474,12 @@ describe("MCPAppsRenderer tool input streaming", () => {
);
});

mockClientConfigStoreState.draftConfig = {
version: 1,
clientCapabilities: {},
hostContext: {
locale: "es-ES",
timeZone: "Europe/Madrid",
deviceCapabilities: {
hover: false,
touch: true,
},
mockHostContextStoreState.draftHostContext = {
locale: "es-ES",
timeZone: "Europe/Madrid",
deviceCapabilities: {
hover: false,
touch: true,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import type { CheckoutSession } from "@/shared/acp-types";
import { listResources, readResource } from "@/lib/apis/mcp-resources-api";
import { listPrompts } from "@/lib/apis/mcp-prompts-api";
import { useChatboxHostStyle } from "@/contexts/chatbox-host-style-context";
import { useClientConfigStore } from "@/stores/client-config-store";
import { useHostContextStore } from "@/stores/host-context-store";
import {
clampDisplayModeToAvailableModes,
extractHostDisplayMode,
Expand Down Expand Up @@ -209,9 +209,7 @@ export function MCPAppsRenderer({
const themeMode = usePreferencesStore((s) => s.themeMode);
const sharedHostStyle = usePreferencesStore((s) => s.hostStyle);
const chatboxHostStyle = useChatboxHostStyle();
const draftHostContext = useClientConfigStore(
(s) => s.draftConfig?.hostContext,
);
const draftHostContext = useHostContextStore((s) => s.draftHostContext);
const baseHostContext = useMemo(
() =>
draftHostContext &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ToolPart } from "../tool-part";
import { useClientConfigStore } from "@/stores/client-config-store";
import { storePresets } from "@/test/mocks";
import { useHostContextStore } from "@/stores/host-context-store";

// Mock lucide-react icons
vi.mock("lucide-react", () => {
Expand Down Expand Up @@ -92,7 +91,19 @@ describe("ToolPart display mode controls", () => {
beforeEach(() => {
vi.clearAllMocks();
onDisplayModeChange = vi.fn();
useClientConfigStore.setState(storePresets.clientConfig());
useHostContextStore.setState({
activeWorkspaceId: null,
defaultHostContext: {},
savedHostContext: undefined,
draftHostContext: {},
hostContextText: "{}",
hostContextError: null,
isSaving: false,
isDirty: false,
pendingWorkspaceId: null,
pendingSavedHostContext: undefined,
isAwaitingRemoteEcho: false,
});
});

const renderWithDisplayModes = (
Expand Down Expand Up @@ -162,9 +173,18 @@ describe("ToolPart display mode controls", () => {
});

it("disables modes that the host does not advertise even when the app supports them", () => {
useClientConfigStore.setState(
storePresets.clientConfigWithHostDisplayModes(["inline"]),
);
useHostContextStore.setState({
draftHostContext: {
availableDisplayModes: ["inline"],
},
hostContextText: JSON.stringify(
{
availableDisplayModes: ["inline"],
},
null,
2,
),
});

renderWithDisplayModes(["inline", "pip", "fullscreen"]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { CspDebugPanel } from "../csp-debug-panel";
import { JsonEditor } from "@/components/ui/json-editor";
import { cn } from "@/lib/chat-utils";
import { TextPart } from "./text-part";
import { useClientConfigStore } from "@/stores/client-config-store";
import { useHostContextStore } from "@/stores/host-context-store";
import { extractHostDisplayModes } from "@/lib/client-config";
import { useChatboxHostTheme } from "@/contexts/chatbox-host-style-context";

Expand Down Expand Up @@ -167,7 +167,7 @@ export function ToolPart({
const widgetDebugInfo = useWidgetDebugStore((s) =>
toolCallId ? s.widgets.get(toolCallId) : undefined,
);
const hostContext = useClientConfigStore((s) => s.draftConfig?.hostContext);
const hostContext = useHostContextStore((s) => s.draftHostContext);
const hostAvailableDisplayModes = useMemo(
() => extractHostDisplayModes(hostContext),
[hostContext],
Expand Down
Loading
Loading