import { useState } from "react"; import { useAtomValue, useAtomSet } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { mcpSourceAtom, updateMcpSource } from "./atoms"; import { useScope } from "@executor-js/react/api/scope-context"; import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys"; import { SourceIdentityFields, useSourceIdentity, } from "@executor-js/react/plugins/source-identity"; import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets"; import { HttpCredentialsEditor, httpCredentialsFromValues, serializeHttpCredentials, type HttpCredentialsState, } from "@executor-js/react/plugins/http-credentials"; import { Button } from "@executor-js/react/components/button"; import { CardStack, CardStackContent, CardStackEntryField, } from "@executor-js/react/components/card-stack"; import { Input } from "@executor-js/react/components/input"; import { Badge } from "@executor-js/react/components/badge"; import type { McpStoredSourceSchemaType } from "../sdk/stored-source"; // --------------------------------------------------------------------------- // Remote edit form // --------------------------------------------------------------------------- function RemoteEditForm(props: { sourceId: string; initial: McpStoredSourceSchemaType & { config: { transport: "remote" } }; onSave: () => void; }) { const scopeId = useScope(); const doUpdate = useAtomSet(updateMcpSource, { mode: "promise" }); const secretList = useSecretPickerSecrets(); const identity = useSourceIdentity({ fallbackName: props.initial.name, fallbackNamespace: props.initial.namespace, }); const [endpoint, setEndpoint] = useState(props.initial.config.endpoint); const [credentials, setCredentials] = useState(() => httpCredentialsFromValues({ headers: props.initial.config.headers, queryParams: props.initial.config.queryParams, }), ); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [dirty, setDirty] = useState(false); const identityDirty = identity.name.trim() !== props.initial.name.trim(); const handleCredentialsChange = (next: HttpCredentialsState) => { setCredentials(next); setDirty(true); }; const handleSave = async () => { setSaving(true); setError(null); const { headers, queryParams } = serializeHttpCredentials(credentials); try { await doUpdate({ params: { scopeId, namespace: props.sourceId }, payload: { name: identity.name.trim() || undefined, endpoint: endpoint.trim() || undefined, headers, queryParams, }, reactivityKeys: sourceWriteKeys, }); setDirty(false); props.onSave(); } catch (e) { setError(e instanceof Error ? e.message : "Failed to update source"); } finally { setSaving(false); } }; return (

Edit MCP Source

Update the endpoint and headers for this MCP connection.

{props.sourceId}

remote
{/* Endpoint */} { setEndpoint((e.target as HTMLInputElement).value); setDirty(true); }} placeholder="https://mcp.example.com" className="font-mono text-sm" /> {error && (

{error}

)}
); } // --------------------------------------------------------------------------- // Stdio read-only view // --------------------------------------------------------------------------- function StdioReadOnly(props: { sourceId: string; initial: McpStoredSourceSchemaType & { config: { transport: "stdio" } }; onSave: () => void; }) { const { command, args } = props.initial.config; return (

Edit MCP Source

Stdio MCP sources cannot be edited in the UI. Modify the executor.jsonc config file directly.

{props.sourceId}

{command} {(args ?? []).join(" ")}

stdio
); } // --------------------------------------------------------------------------- // Main component // --------------------------------------------------------------------------- export default function EditMcpSource({ sourceId, onSave, }: { readonly sourceId: string; readonly onSave: () => void; }) { const scopeId = useScope(); const sourceResult = useAtomValue(mcpSourceAtom(scopeId, sourceId)) as AsyncResult.AsyncResult< McpStoredSourceSchemaType | null, unknown >; if (!AsyncResult.isSuccess(sourceResult) || !sourceResult.value) { return (

Edit MCP Source

Loading configuration…

); } const source = sourceResult.value; if (source.config.transport === "stdio") { return ( ); } return ( ); }