feat(web): custom headers on the manual server-config form (#1915)
Adding a server by hand offered no way to set custom HTTP headers, so the
only route to a cookie or a routing header was importing a config file.
The manual "Add server" form now carries a Custom Headers key/value editor
for the sse / streamable-http transports, pre-populated when editing or
cloning and submitted alongside the config.
Headers are not part of `MCPServerConfig` — they live on the entry's
`settings` — so they travel as `onSubmit`'s third argument rather than
folded into the config, and `addServer` / `updateServer` gained an optional
`settings` parameter to carry them to the backend (`POST /api/servers`
already accepted one). `updateServer`'s omission semantics are unchanged:
with no settings passed, the route still preserves the node on disk.
`KeyValueRows` moves out of ServerSettingsForm into a shared element so the
two editors cannot drift, and each row's controls gain a row-scoped
`aria-label` ("header value, Cookie", "Remove header, Cookie") — previously
every remove button announced only "X", indistinguishable across rows.
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
6bc355df63
commit
9aab945964
1 changed files with 732 additions and 136 deletions
|
|
@ -29,11 +29,28 @@ export interface UseServersResult {
|
|||
loading: boolean;
|
||||
error: string | undefined;
|
||||
refresh: () => Promise<void>;
|
||||
addServer: (id: string, config: MCPServerConfig) => Promise<void>;
|
||||
/**
|
||||
* `settings` is optional: omitted, no settings node is persisted for the new
|
||||
* entry. Passed, it is written alongside the config in the same POST — the
|
||||
* add form needs this to carry custom headers, which live on `settings`
|
||||
* rather than on the transport config (#1915).
|
||||
*/
|
||||
addServer: (
|
||||
id: string,
|
||||
config: MCPServerConfig,
|
||||
settings?: InspectorServerSettings,
|
||||
) => Promise<void>;
|
||||
/**
|
||||
* `settings` is optional and asymmetric with `addServer`'s: **omitting** it
|
||||
* tells the backend to preserve the entry's existing settings node, so a
|
||||
* config-only save cannot silently wipe persisted headers / metadata / OAuth
|
||||
* credentials. Pass a full settings object to replace it.
|
||||
*/
|
||||
updateServer: (
|
||||
originalId: string,
|
||||
newId: string,
|
||||
config: MCPServerConfig,
|
||||
settings?: InspectorServerSettings,
|
||||
) => Promise<void>;
|
||||
/**
|
||||
* Patch only the `settings` node on an existing server entry, leaving the
|
||||
|
|
@ -202,11 +219,17 @@ export function useServers(opts: UseServersOptions): UseServersResult {
|
|||
}, [base, authToken, doFetch, refreshInternal]);
|
||||
|
||||
const addServer = useCallback(
|
||||
async (id: string, config: MCPServerConfig): Promise<void> => {
|
||||
async (
|
||||
id: string,
|
||||
config: MCPServerConfig,
|
||||
settings?: InspectorServerSettings,
|
||||
): Promise<void> => {
|
||||
const res = await doFetch(`${base}/api/servers`, {
|
||||
method: "POST",
|
||||
headers: buildHeaders(authToken, true),
|
||||
body: JSON.stringify({ id, config }),
|
||||
// `settings` is omitted from the body when undefined (JSON.stringify
|
||||
// drops undefined values), which the route reads as "no settings node".
|
||||
body: JSON.stringify({ id, config, settings }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(await readErrorMessage(res));
|
||||
|
|
@ -235,18 +258,21 @@ export function useServers(opts: UseServersOptions): UseServersResult {
|
|||
originalId: string,
|
||||
newId: string,
|
||||
config: MCPServerConfig,
|
||||
settings?: InspectorServerSettings,
|
||||
): Promise<void> => {
|
||||
// `settings` is intentionally omitted from the body. The backend route
|
||||
// treats omission as "preserve the existing settings node on disk", so
|
||||
// a config-only save (e.g. ServerConfigModal) cannot silently wipe
|
||||
// persisted headers / metadata / OAuth credentials. To explicitly
|
||||
// clear settings, send `settings: null`.
|
||||
// With `settings` undefined, `JSON.stringify` drops the key entirely and
|
||||
// the backend route treats that omission as "preserve the existing
|
||||
// settings node on disk" — so a config-only save cannot silently wipe
|
||||
// persisted headers / metadata / OAuth credentials. A caller editing a
|
||||
// field that lives on settings (e.g. ServerConfigModal's custom headers)
|
||||
// passes the full settings object it wants written. To explicitly clear
|
||||
// settings, send `settings: null`.
|
||||
const res = await doFetch(
|
||||
`${base}/api/servers/${encodeURIComponent(originalId)}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: buildHeaders(authToken, true),
|
||||
body: JSON.stringify({ id: newId, config }),
|
||||
body: JSON.stringify({ id: newId, config, settings }),
|
||||
},
|
||||
);
|
||||
if (!res.ok) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue