(props: {
onComplete: (slug?: string) => void;
onCancel: () => void;
initialUrl?: string;
initialPreset?: string;
/** Whether the stdio transport is enabled on the server. */
allowStdio?: boolean;
})
| 162 | // --------------------------------------------------------------------------- |
| 163 | |
| 164 | export default function AddMcpSource(props: { |
| 165 | onComplete: (slug?: string) => void; |
| 166 | onCancel: () => void; |
| 167 | initialUrl?: string; |
| 168 | initialPreset?: string; |
| 169 | /** Whether the stdio transport is enabled on the server. */ |
| 170 | allowStdio?: boolean; |
| 171 | }) { |
| 172 | const allowStdio = props.allowStdio ?? false; |
| 173 | const rawPreset = findPreset(props.initialPreset); |
| 174 | // Drop stdio presets when stdio is disabled — the caller should have |
| 175 | // already filtered these out, but defence-in-depth. |
| 176 | const preset = rawPreset?.transport === "stdio" && !allowStdio ? undefined : rawPreset; |
| 177 | const isStdioPreset = preset?.transport === "stdio"; |
| 178 | |
| 179 | const [transport, setTransport] = useState<"remote" | "stdio">( |
| 180 | isStdioPreset && allowStdio ? "stdio" : "remote", |
| 181 | ); |
| 182 | |
| 183 | // --- Stdio state --- |
| 184 | const [stdioCommand, setStdioCommand] = useState(isStdioPreset ? preset.command : ""); |
| 185 | const [stdioArgs, setStdioArgs] = useState( |
| 186 | isStdioPreset && preset.args ? preset.args.join(" ") : "", |
| 187 | ); |
| 188 | const [stdioEnvVars, setStdioEnvVars] = useState<string[]>([]); |
| 189 | const stdioIdentity = useIntegrationIdentity({ |
| 190 | fallbackName: isStdioPreset |
| 191 | ? preset.name |
| 192 | : (integrationDisplayNameFromStdio(stdioCommand, parseStdioArgs(stdioArgs), "MCP") ?? |
| 193 | stdioCommand), |
| 194 | }); |
| 195 | const [stdioAdding, setStdioAdding] = useState(false); |
| 196 | const [stdioError, setStdioError] = useState<string | null>(null); |
| 197 | |
| 198 | // --- Remote state --- |
| 199 | const remoteUrl = |
| 200 | !isStdioPreset && preset?.transport === undefined && preset?.url |
| 201 | ? preset.url |
| 202 | : (props.initialUrl ?? ""); |
| 203 | |
| 204 | const [state, dispatch] = useReducer( |
| 205 | reducer, |
| 206 | remoteUrl ? { step: "url" as const, url: remoteUrl } : init, |
| 207 | ); |
| 208 | |
| 209 | const doProbe = useAtomSet(probeMcpEndpoint, { mode: "promiseExit" }); |
| 210 | const doAddServer = useAtomSet(addMcpServer, { mode: "promiseExit" }); |
| 211 | |
| 212 | const probe = "probe" in state ? state.probe : null; |
| 213 | |
| 214 | // The probe seeds the method list: detected OAuth → an OAuth row; a 401 |
| 215 | // without OAuth metadata → a bearer-header row; an open server → a no-auth |
| 216 | // row. The user can edit any row or add alternate methods alongside. |
| 217 | const authMethodSeeds: readonly AuthMethodSeed[] = useMemo(() => { |
| 218 | if (!probe) return []; |
| 219 | if (probe.requiresOAuth) { |
| 220 | return [ |
| 221 | { |
nothing calls this directly
no test coverage detected