* feat(web,core): import client configs and registry server.json (#1348) Wire up the two server-import affordances that were previously todoNoop: - Import config: source picker (Claude Desktop / Cursor / Cline / VS Code) read on the backend via a new GET /api/import-source, plus a file-upload fallback. Per-server review with import/skip on new servers and overwrite/skip/rename on id conflicts, then a per-server outcome summary. - Import server.json: parse the MCP registry single-server format (npm/pypi/oci packages + remotes, env vars, package/runtime args, URL template vars) into a runnable config, with a file picker and debounced live validation; Add Server is disabled until validation passes. Core gains an isomorphic import strategy layer (core/mcp/import) with a strategy registry, client-config + server.json parsers, a strategy-agnostic merge, and a dependency-injected source resolver. useServers exposes importSource(). Modal headers are bold and the redundant server.json section header is folded into the modal title. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): collapse the server.json textarea into a File Contents disclosure Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): auto-collapse File Contents disclosure after content loads Once content is pasted or a file is loaded, collapse the File Contents disclosure after ~1s with Mantine's built-in accordion animation, signaling the input was accepted and surfacing the validation/env-var sections. The disclosure becomes controlled by the modal; clearing the textarea re-opens it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): flash File Contents hover highlight before auto-collapse Before the File Contents disclosure auto-collapses, paint it with its hover background for 250ms, then run the collapse over a 325ms animation, so the collapse reads as intentional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import server.json" to "Import from registry config" Rename the Add Servers menu entry and the modal header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import config" to "Import client config" Rename the Add Servers menu entry and the modal header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web,core): move import orchestration into core helpers + web hooks Make the import logic reusable by CLI/TUI and keep the modals display-only: - core/mcp/import: add parseClientConfig (mcpServers→servers fallback), resolveServerId, selectServerJsonOption, and buildServerConfigForSelection, so the full import (parse → select → build → merge) is callable from any environment. - core/storage: extract the pure validateStoreId into a Node-free store-id module (store-io re-exports it) so the isomorphic import layer can validate ids without pulling Node deps into the browser bundle. - clients/web/hooks: useServerJsonImport and useImportClientConfig own all the wizard/parse/debounce/apply state; ServerImportConfigModal and ServerImportJsonModal are now thin display shells over the dumb panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import client config" to "Import from client config" Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): remove the Cancel button from the registry import modal The modal header X (and Escape) still dismiss it; drop the redundant Cancel button (and the panel onCancel prop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): hide validation results + name override until content is present In the registry import modal, the Validation Results section and the Server Name override field stay hidden until something has been pasted/loaded into the textarea. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make Validation Results a collapsible disclosure Wrap the registry import modal's validation results in a Mantine accordion disclosure (default open), matching the File Contents section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Server Name disclosure with read-only derived name + override Make Server Name a collapsible disclosure containing a read-only "From configuration" field (the id derived from the server.json name) and the editable Override field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): scroll to and highlight a freshly-added server After a manual add/clone or an import (client config / registry), the new server's card scrolls into view and draws an animated border (Mantine Border Animate); clicking anywhere on the card dismisses it. - App tracks highlightedServerId; addServerHighlighted sets it (edits and conflict-overwrites don't). Threaded through InspectorView -> ServerListScreen -> ServerCard. - ServerCard scrolls into view on highlight and wraps in <BorderAnimate>; onClick clears the highlight. - Pin @gfazioli/mantine-border-animate@2.0.3 (Mantine 8 compatible; 2.0.4+ requires Mantine 9). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): highlighted card fills its cell and its controls work on first click - The BorderAnimate wrapper is display:flex, so the card was shrinking to content width while the border spanned the cell. Stretch the card with w=100% (and the wrapper). - Always render BorderAnimate and toggle show/animate instead of conditionally wrapping: swapping the element type on clear remounted the card, swallowing the click that triggered it (e.g. the connect toggle fired clear but not connect, needing a second click). Verified: one click now connects + clears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web,core): address review — OCI env, untrack fixture, rename validation - Untrack the stray inspector-v1-multi.server.json (accidentally committed) and gitignore root *.server.json fixtures. - core/mcp/import/serverJson: forward declared env vars into OCI containers via '-e KEY' docker args (they previously only set the docker CLI process env); add a test. Clarify buildServerConfig (destructure env out instead of spread-then-delete) and document value-vs-default env handling. - useImportClientConfig: validate 'rename' targets inline (invalid charset / collision with existing, additions, or other renames) and gate the Import button on canImport; surface the error on the field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): highlight all servers from a batch import, not just the last Track highlighted servers as a set of ids instead of one. addServerHighlighted accumulates each id (a multi-server client-config import highlights them all); opening an add/import modal starts a fresh batch. Clicking a card clears only that card's highlight; only the first highlighted card scrolls into view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5efe9ddc7b
commit
97b1a7fbff
1 changed files with 4238 additions and 176 deletions
|
|
@ -107,6 +107,8 @@ import {
|
|||
type ServerConfigModalMode,
|
||||
} from "./components/groups/ServerConfigModal/ServerConfigModal";
|
||||
import { ServerSettingsModal } from "./components/groups/ServerSettingsModal/ServerSettingsModal";
|
||||
import { ServerImportConfigModal } from "./components/groups/ServerImportConfigModal/ServerImportConfigModal";
|
||||
import { ServerImportJsonModal } from "./components/groups/ServerImportJsonModal/ServerImportJsonModal";
|
||||
import { ConnectionInfoModal } from "./components/groups/ConnectionInfoModal/ConnectionInfoModal";
|
||||
import { OutputValidationModal } from "./components/groups/OutputValidationModal/OutputValidationModal";
|
||||
import { UrlElicitationErrorModal } from "./components/groups/UrlElicitationErrorModal/UrlElicitationErrorModal";
|
||||
|
|
@ -489,6 +491,7 @@ function App() {
|
|||
updateServerSettings,
|
||||
removeServer,
|
||||
reorderServers,
|
||||
importSource,
|
||||
} = useServers({
|
||||
baseUrl:
|
||||
typeof window !== "undefined"
|
||||
|
|
@ -503,6 +506,23 @@ function App() {
|
|||
mode: ServerConfigModalMode;
|
||||
targetId?: string;
|
||||
} | null>(null);
|
||||
// Import-flow modals (#1348): "Import from client config" (other-client
|
||||
// config merge) and "Import from registry config" (registry single-server
|
||||
// import).
|
||||
const [importConfigOpen, setImportConfigOpen] = useState(false);
|
||||
const [importJsonOpen, setImportJsonOpen] = useState(false);
|
||||
// Ids of freshly-added servers (manual or import) — their cards draw an
|
||||
// animated border (and the first scrolls into view) until clicked. A batch
|
||||
// import accumulates all of its ids here; opening an add/import modal starts a
|
||||
// fresh batch. (#1348)
|
||||
const [highlightedServerIds, setHighlightedServerIds] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
const clearHighlight = useCallback(
|
||||
(id: string) =>
|
||||
setHighlightedServerIds((ids) => ids.filter((x) => x !== id)),
|
||||
[],
|
||||
);
|
||||
const [settingsModalTargetId, setSettingsModalTargetId] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
|
|
@ -2083,14 +2103,6 @@ function App() {
|
|||
);
|
||||
}, [logs, activeServerId]);
|
||||
|
||||
// Action stubs — these UI affordances exist but require additional
|
||||
// wiring (server CRUD, history pinning, app sandbox round-trip, log
|
||||
// export). Tracked separately; the noop keeps the prop interface
|
||||
// satisfied without lying about behavior.
|
||||
const todoNoop = useCallback(() => {
|
||||
/* TODO: not wired yet */
|
||||
}, []);
|
||||
|
||||
// Download the current server list as a canonical mcp.json file. Uses the
|
||||
// in-memory `servers` list (kept in sync with disk by useServers' refresh-
|
||||
// after-mutate flow) so there's no extra HTTP roundtrip. Serialization
|
||||
|
|
@ -2156,6 +2168,20 @@ function App() {
|
|||
|
||||
// Submit handler for the Add / Edit / Clone modal. Add and Clone both go
|
||||
// through addServer; Edit uses updateServer (which supports id rename).
|
||||
// Add a server, then mark it as the freshly-added one so the list scrolls to
|
||||
// it and highlights it. Used by manual add/clone and both import flows; edits
|
||||
// and conflict-overwrites (updateServer) intentionally don't highlight.
|
||||
// Accumulates into the current highlight batch (a multi-server import adds
|
||||
// each id), deduped. The batch is reset to empty when an add/import modal
|
||||
// opens (see the menu handlers).
|
||||
const addServerHighlighted = useCallback(
|
||||
async (id: string, config: MCPServerConfig) => {
|
||||
await addServer(id, config);
|
||||
setHighlightedServerIds((ids) => (ids.includes(id) ? ids : [...ids, id]));
|
||||
},
|
||||
[addServer],
|
||||
);
|
||||
|
||||
// On rename of the active server, keep activeServerId pointed at the new id.
|
||||
const onConfigSubmit = useCallback(
|
||||
async (id: string, config: MCPServerConfig) => {
|
||||
|
|
@ -2168,9 +2194,9 @@ function App() {
|
|||
return;
|
||||
}
|
||||
// add or clone
|
||||
await addServer(id, config);
|
||||
await addServerHighlighted(id, config);
|
||||
},
|
||||
[configModal, addServer, updateServer, activeServerId],
|
||||
[configModal, addServerHighlighted, updateServer, activeServerId],
|
||||
);
|
||||
|
||||
// Derive the existingIds list the modal uses for uniqueness validation.
|
||||
|
|
@ -2399,14 +2425,26 @@ function App() {
|
|||
onDisconnect={() => {
|
||||
void onDisconnect();
|
||||
}}
|
||||
onServerAdd={() => setConfigModal({ mode: "add" })}
|
||||
onServerImportConfig={todoNoop}
|
||||
onServerImportJson={todoNoop}
|
||||
onServerAdd={() => {
|
||||
setHighlightedServerIds([]);
|
||||
setConfigModal({ mode: "add" });
|
||||
}}
|
||||
onServerImportConfig={() => {
|
||||
setHighlightedServerIds([]);
|
||||
setImportConfigOpen(true);
|
||||
}}
|
||||
onServerImportJson={() => {
|
||||
setHighlightedServerIds([]);
|
||||
setImportJsonOpen(true);
|
||||
}}
|
||||
onServerExport={onServerExport}
|
||||
onConnectionInfo={() => setConnectionInfoModalOpen(true)}
|
||||
onServerSettings={(id) => setSettingsModalTargetId(id)}
|
||||
onServerEdit={(id) => setConfigModal({ mode: "edit", targetId: id })}
|
||||
onServerClone={(id) => setConfigModal({ mode: "clone", targetId: id })}
|
||||
onServerClone={(id) => {
|
||||
setHighlightedServerIds([]);
|
||||
setConfigModal({ mode: "clone", targetId: id });
|
||||
}}
|
||||
onServerRemove={(id) => {
|
||||
const target = servers.find((s) => s.id === id);
|
||||
if (target) setRemoveTarget(target);
|
||||
|
|
@ -2425,6 +2463,8 @@ function App() {
|
|||
});
|
||||
});
|
||||
}}
|
||||
highlightedServerIds={highlightedServerIds}
|
||||
onClearHighlight={clearHighlight}
|
||||
serverSupportsTaskToolCalls={
|
||||
!!capabilities?.tasks?.requests?.tools?.call
|
||||
}
|
||||
|
|
@ -2490,6 +2530,20 @@ function App() {
|
|||
onClose={() => setConfigModal(null)}
|
||||
onSubmit={onConfigSubmit}
|
||||
/>
|
||||
<ServerImportConfigModal
|
||||
opened={importConfigOpen}
|
||||
existingIds={existingIds}
|
||||
onClose={() => setImportConfigOpen(false)}
|
||||
onFetchSource={importSource}
|
||||
onAddServer={addServerHighlighted}
|
||||
onUpdateServer={updateServer}
|
||||
/>
|
||||
<ServerImportJsonModal
|
||||
opened={importJsonOpen}
|
||||
existingIds={existingIds}
|
||||
onClose={() => setImportJsonOpen(false)}
|
||||
onAddServer={addServerHighlighted}
|
||||
/>
|
||||
<ServerSettingsModal
|
||||
// Remount per open (and per target server) so the accordion resets to
|
||||
// its initial "options" section — the body-dropped toast deep-links
|
||||
|
|
|
|||
Loading…
Reference in a new issue