Fix MCP form input and add reactive MCP server state
- Fix space key not working in MCP add form by handling evt.name === "space"
- Add support for multi-character paste sequences via evt.sequence
- Add MCPServerDisplay type to types/tui.ts for UI display
- Add mcpServers reactive state to app store with setMcpServers,
addMcpServer, and updateMcpServerStatus actions
- Update session.tsx to use store's mcpServers instead of static props
- Update execute.tsx to update store when server is added/connected
- Remove duplicate MCPServer interfaces from app.tsx, session.tsx,
and mcp-select.tsx in favor of shared MCPServerDisplay type
This commit is contained in:
@@ -105,7 +105,20 @@ const defaultHandleMCPAdd = async (data: MCPAddFormData): Promise<void> => {
|
||||
data.isGlobal,
|
||||
);
|
||||
|
||||
await connectServer(data.name);
|
||||
// Add to store with "connecting" status
|
||||
appStore.addMcpServer({
|
||||
id: data.name,
|
||||
name: data.name,
|
||||
status: "disconnected",
|
||||
description: data.command,
|
||||
});
|
||||
|
||||
try {
|
||||
await connectServer(data.name);
|
||||
appStore.updateMcpServerStatus(data.name, "connected");
|
||||
} catch {
|
||||
appStore.updateMcpServerStatus(data.name, "error");
|
||||
}
|
||||
};
|
||||
|
||||
const defaultHandleBrainSetJwtToken = async (jwtToken: string): Promise<void> => {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ToastProvider, Toast, useToast } from "@tui-solid/ui/toast";
|
||||
import { Home } from "@tui-solid/routes/home";
|
||||
import { Session } from "@tui-solid/routes/session";
|
||||
import type { TuiInput, TuiOutput } from "@tui-solid/types";
|
||||
import type { MCPServerDisplay } from "@/types/tui";
|
||||
import type { PermissionScope, LearningScope } from "@/types/tui";
|
||||
import type { MCPAddFormData } from "@/types/mcp";
|
||||
|
||||
@@ -37,13 +38,6 @@ interface AgentOption {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface MCPServer {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "connected" | "disconnected" | "error";
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface AppProps extends TuiInput {
|
||||
onExit: (output: TuiOutput) => void;
|
||||
onSubmit: (input: string) => Promise<void>;
|
||||
@@ -72,7 +66,7 @@ interface AppProps extends TuiInput {
|
||||
} | null;
|
||||
agents?: AgentOption[];
|
||||
currentAgent?: string;
|
||||
mcpServers?: MCPServer[];
|
||||
mcpServers?: MCPServerDisplay[];
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
@@ -450,7 +444,7 @@ export interface TuiRenderOptions extends TuiInput {
|
||||
} | null;
|
||||
agents?: AgentOption[];
|
||||
currentAgent?: string;
|
||||
mcpServers?: MCPServer[];
|
||||
mcpServers?: MCPServerDisplay[];
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@ import { createSignal, createMemo, For, Show } from "solid-js";
|
||||
import { useKeyboard } from "@opentui/solid";
|
||||
import { TextAttributes } from "@opentui/core";
|
||||
import { useTheme } from "@tui-solid/context/theme";
|
||||
|
||||
interface MCPServer {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "connected" | "disconnected" | "error";
|
||||
description?: string;
|
||||
}
|
||||
import type { MCPServerDisplay } from "@/types/tui";
|
||||
|
||||
interface MCPSelectProps {
|
||||
servers: MCPServer[];
|
||||
servers: MCPServerDisplay[];
|
||||
onSelect: (serverId: string) => void;
|
||||
onAddNew: () => void;
|
||||
onClose: () => void;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<MCPServer["status"], string> = {
|
||||
const STATUS_COLORS: Record<MCPServerDisplay["status"], string> = {
|
||||
connected: "success",
|
||||
disconnected: "textDim",
|
||||
error: "error",
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
CommandMenuState,
|
||||
StreamingLogState,
|
||||
SuggestionState,
|
||||
MCPServerDisplay,
|
||||
} from "@/types/tui";
|
||||
import type { ProviderModel } from "@/types/providers";
|
||||
import type { BrainConnectionStatus, BrainUser } from "@/types/brain";
|
||||
@@ -45,6 +46,7 @@ interface AppStore {
|
||||
streamingLog: StreamingLogState;
|
||||
suggestions: SuggestionState;
|
||||
cascadeEnabled: boolean;
|
||||
mcpServers: MCPServerDisplay[];
|
||||
brain: {
|
||||
status: BrainConnectionStatus;
|
||||
user: BrainUser | null;
|
||||
@@ -89,6 +91,7 @@ interface AppContextValue {
|
||||
streamingLogIsActive: Accessor<boolean>;
|
||||
suggestions: Accessor<SuggestionState>;
|
||||
cascadeEnabled: Accessor<boolean>;
|
||||
mcpServers: Accessor<MCPServerDisplay[]>;
|
||||
brain: Accessor<{
|
||||
status: BrainConnectionStatus;
|
||||
user: BrainUser | null;
|
||||
@@ -184,6 +187,11 @@ interface AppContextValue {
|
||||
setBrainShowBanner: (show: boolean) => void;
|
||||
dismissBrainBanner: () => void;
|
||||
|
||||
// MCP actions
|
||||
setMcpServers: (servers: MCPServerDisplay[]) => void;
|
||||
addMcpServer: (server: MCPServerDisplay) => void;
|
||||
updateMcpServerStatus: (id: string, status: MCPServerDisplay["status"]) => void;
|
||||
|
||||
// Computed
|
||||
isInputLocked: () => boolean;
|
||||
}
|
||||
@@ -249,6 +257,7 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
streamingLog: createInitialStreamingState(),
|
||||
suggestions: createInitialSuggestionState(),
|
||||
cascadeEnabled: true,
|
||||
mcpServers: [],
|
||||
brain: {
|
||||
status: "disconnected" as BrainConnectionStatus,
|
||||
user: null,
|
||||
@@ -303,6 +312,7 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
const streamingLogIsActive = (): boolean => store.streamingLog.isStreaming;
|
||||
const suggestions = (): SuggestionState => store.suggestions;
|
||||
const cascadeEnabled = (): boolean => store.cascadeEnabled;
|
||||
const mcpServers = (): MCPServerDisplay[] => store.mcpServers;
|
||||
const brain = () => store.brain;
|
||||
|
||||
// Mode actions
|
||||
@@ -522,6 +532,36 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
setStore("brain", { ...store.brain, showBanner: false });
|
||||
};
|
||||
|
||||
// MCP actions
|
||||
const setMcpServers = (servers: MCPServerDisplay[]): void => {
|
||||
setStore("mcpServers", servers);
|
||||
};
|
||||
|
||||
const addMcpServer = (server: MCPServerDisplay): void => {
|
||||
setStore(
|
||||
produce((s) => {
|
||||
// Replace if exists, otherwise add
|
||||
const existingIndex = s.mcpServers.findIndex((srv) => srv.id === server.id);
|
||||
if (existingIndex !== -1) {
|
||||
s.mcpServers[existingIndex] = server;
|
||||
} else {
|
||||
s.mcpServers.push(server);
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const updateMcpServerStatus = (id: string, status: MCPServerDisplay["status"]): void => {
|
||||
setStore(
|
||||
produce((s) => {
|
||||
const server = s.mcpServers.find((srv) => srv.id === id);
|
||||
if (server) {
|
||||
server.status = status;
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Session stats actions
|
||||
const startThinking = (): void => {
|
||||
setStore("sessionStats", {
|
||||
@@ -758,6 +798,7 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
streamingLogIsActive,
|
||||
suggestions,
|
||||
cascadeEnabled,
|
||||
mcpServers,
|
||||
brain,
|
||||
|
||||
// Mode actions
|
||||
@@ -820,6 +861,11 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
setBrainShowBanner,
|
||||
dismissBrainBanner,
|
||||
|
||||
// MCP actions
|
||||
setMcpServers,
|
||||
addMcpServer,
|
||||
updateMcpServerStatus,
|
||||
|
||||
// Session stats actions
|
||||
startThinking,
|
||||
stopThinking,
|
||||
@@ -887,6 +933,7 @@ const defaultAppState = {
|
||||
isCompacting: false,
|
||||
streamingLog: createInitialStreamingState(),
|
||||
suggestions: createInitialSuggestionState(),
|
||||
mcpServers: [] as MCPServerDisplay[],
|
||||
brain: {
|
||||
status: "disconnected" as BrainConnectionStatus,
|
||||
user: null,
|
||||
@@ -926,6 +973,7 @@ export const appStore = {
|
||||
isCompacting: storeRef.isCompacting(),
|
||||
streamingLog: storeRef.streamingLog(),
|
||||
suggestions: storeRef.suggestions(),
|
||||
mcpServers: storeRef.mcpServers(),
|
||||
brain: storeRef.brain(),
|
||||
};
|
||||
},
|
||||
@@ -1134,4 +1182,19 @@ export const appStore = {
|
||||
if (!storeRef) return;
|
||||
storeRef.dismissBrainBanner();
|
||||
},
|
||||
|
||||
setMcpServers: (servers: MCPServerDisplay[]): void => {
|
||||
if (!storeRef) return;
|
||||
storeRef.setMcpServers(servers);
|
||||
},
|
||||
|
||||
addMcpServer: (server: MCPServerDisplay): void => {
|
||||
if (!storeRef) return;
|
||||
storeRef.addMcpServer(server);
|
||||
},
|
||||
|
||||
updateMcpServerStatus: (id: string, status: MCPServerDisplay["status"]): void => {
|
||||
if (!storeRef) return;
|
||||
storeRef.updateMcpServerStatus(id, status);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Show, Switch, Match, createSignal } from "solid-js";
|
||||
import { Show, Switch, Match, createSignal, createMemo, onMount } from "solid-js";
|
||||
import { useTheme } from "@tui-solid/context/theme";
|
||||
import { useAppStore } from "@tui-solid/context/app";
|
||||
import { Header } from "@tui-solid/components/header";
|
||||
@@ -23,7 +23,7 @@ import { CenteredModal } from "@tui-solid/components/centered-modal";
|
||||
import { DebugLogPanel } from "@tui-solid/components/debug-log-panel";
|
||||
import { BrainMenu } from "@tui-solid/components/brain-menu";
|
||||
import { BRAIN_DISABLED } from "@constants/brain";
|
||||
import type { PermissionScope, LearningScope, InteractionMode } from "@/types/tui";
|
||||
import type { PermissionScope, LearningScope, InteractionMode, MCPServerDisplay } from "@/types/tui";
|
||||
import type { MCPAddFormData } from "@/types/mcp";
|
||||
|
||||
interface AgentOption {
|
||||
@@ -32,13 +32,6 @@ interface AgentOption {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface MCPServer {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "connected" | "disconnected" | "error";
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface ProviderStatus {
|
||||
available: boolean;
|
||||
error?: string;
|
||||
@@ -72,7 +65,7 @@ interface SessionProps {
|
||||
} | null;
|
||||
agents?: AgentOption[];
|
||||
currentAgent?: string;
|
||||
mcpServers?: MCPServer[];
|
||||
mcpServers?: MCPServerDisplay[];
|
||||
files?: string[];
|
||||
providerStatuses?: Record<string, ProviderStatus>;
|
||||
providerScores?: Record<string, number>;
|
||||
@@ -82,6 +75,16 @@ export function Session(props: SessionProps) {
|
||||
const theme = useTheme();
|
||||
const app = useAppStore();
|
||||
|
||||
// Initialize MCP servers from props on mount
|
||||
onMount(() => {
|
||||
if (props.mcpServers && props.mcpServers.length > 0) {
|
||||
app.setMcpServers(props.mcpServers);
|
||||
}
|
||||
});
|
||||
|
||||
// Use store's mcpServers (reactive, updated when servers are added)
|
||||
const mcpServers = createMemo(() => app.mcpServers());
|
||||
|
||||
// Local state for help menu
|
||||
const [selectedHelpTopic, setSelectedHelpTopic] = createSignal<string | null>(
|
||||
null
|
||||
@@ -291,7 +294,7 @@ export function Session(props: SessionProps) {
|
||||
<Match when={app.mode() === "mcp_select"}>
|
||||
<CenteredModal>
|
||||
<MCPSelect
|
||||
servers={props.mcpServers ?? []}
|
||||
servers={mcpServers()}
|
||||
onSelect={props.onMCPSelect}
|
||||
onAddNew={handleMCPAddNew}
|
||||
onClose={handleMCPClose}
|
||||
|
||||
@@ -246,6 +246,19 @@ export interface StreamingLogState {
|
||||
isStreaming: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MCP Types (for UI display)
|
||||
// ============================================================================
|
||||
|
||||
export type MCPServerStatus = "connected" | "disconnected" | "error";
|
||||
|
||||
export interface MCPServerDisplay {
|
||||
id: string;
|
||||
name: string;
|
||||
status: MCPServerStatus;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Component Props Types
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user