@executor-js/plugin-mcp 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/AddMcpSource-VM3HY26S.js +762 -0
  2. package/dist/AddMcpSource-VM3HY26S.js.map +1 -0
  3. package/dist/EditMcpSource-WELWGRJG.js +259 -0
  4. package/dist/EditMcpSource-WELWGRJG.js.map +1 -0
  5. package/dist/McpSourceSummary-7TDQXLT5.js +85 -0
  6. package/dist/McpSourceSummary-7TDQXLT5.js.map +1 -0
  7. package/dist/api/group.d.ts +109 -17
  8. package/dist/api/index.d.ts +391 -0
  9. package/dist/chunk-2ETJ6LQH.js +239 -0
  10. package/dist/chunk-2ETJ6LQH.js.map +1 -0
  11. package/dist/chunk-OOOH3IO4.js +2194 -0
  12. package/dist/chunk-OOOH3IO4.js.map +1 -0
  13. package/dist/chunk-SKSXXFOA.js +104 -0
  14. package/dist/chunk-SKSXXFOA.js.map +1 -0
  15. package/dist/chunk-Z4CRPOLI.js +186 -0
  16. package/dist/chunk-Z4CRPOLI.js.map +1 -0
  17. package/dist/chunk-ZIRGIRGP.js +115 -0
  18. package/dist/chunk-ZIRGIRGP.js.map +1 -0
  19. package/dist/client.js +51 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/core.js +26 -2
  22. package/dist/index.js +2 -1
  23. package/dist/react/McpRemoteSourceFields.d.ts +18 -0
  24. package/dist/react/McpSourceSummary.d.ts +5 -0
  25. package/dist/react/atoms.d.ts +206 -6
  26. package/dist/react/client.d.ts +113 -16
  27. package/dist/react/index.d.ts +1 -1
  28. package/dist/react/plugin-client.d.ts +9 -2
  29. package/dist/sdk/binding-store.d.ts +106 -1
  30. package/dist/sdk/index.d.ts +1 -1
  31. package/dist/sdk/invoke.d.ts +2 -0
  32. package/dist/sdk/plugin.d.ts +142 -114
  33. package/dist/sdk/probe-shape-real-servers.live.test.d.ts +1 -0
  34. package/dist/sdk/probe-shape.d.ts +17 -3
  35. package/dist/sdk/stored-source.d.ts +9 -6
  36. package/dist/sdk/types.d.ts +138 -13
  37. package/dist/{stdio-connector-KNHLETKM.js → stdio-connector-AA5S6UUJ.js} +1 -1
  38. package/dist/{stdio-connector-KNHLETKM.js.map → stdio-connector-AA5S6UUJ.js.map} +1 -1
  39. package/dist/testing/index.d.ts +1 -0
  40. package/dist/{sdk/test-utils.d.ts → testing/server.d.ts} +0 -6
  41. package/dist/testing.js +51 -0
  42. package/dist/testing.js.map +1 -0
  43. package/package.json +17 -4
  44. package/dist/chunk-C2GNZGFJ.js +0 -1622
  45. package/dist/chunk-C2GNZGFJ.js.map +0 -1
@@ -0,0 +1,762 @@
1
+ import {
2
+ mcpPresets
3
+ } from "./chunk-SKSXXFOA.js";
4
+ import {
5
+ McpRemoteSourceFields
6
+ } from "./chunk-ZIRGIRGP.js";
7
+ import {
8
+ addMcpSourceOptimistic,
9
+ probeMcpEndpoint
10
+ } from "./chunk-2ETJ6LQH.js";
11
+ import {
12
+ MCP_OAUTH_CONNECTION_SLOT
13
+ } from "./chunk-Z4CRPOLI.js";
14
+
15
+ // src/react/AddMcpSource.tsx
16
+ import { useReducer, useCallback, useEffect, useRef, useState } from "react";
17
+ import { useAtomSet } from "@effect/atom-react";
18
+ import * as Exit from "effect/Exit";
19
+ import * as Match from "effect/Match";
20
+ import * as Option from "effect/Option";
21
+ import * as Schema from "effect/Schema";
22
+ import { useScope } from "@executor-js/react/api/scope-context";
23
+ import { Button } from "@executor-js/react/components/button";
24
+ import {
25
+ CardStack,
26
+ CardStackContent,
27
+ CardStackEntry,
28
+ CardStackEntryField
29
+ } from "@executor-js/react/components/card-stack";
30
+ import { FieldLabel } from "@executor-js/react/components/field";
31
+ import { FilterTabs } from "@executor-js/react/components/filter-tabs";
32
+ import { FloatActions } from "@executor-js/react/components/float-actions";
33
+ import { Input } from "@executor-js/react/components/input";
34
+ import { Label } from "@executor-js/react/components/label";
35
+ import { Spinner } from "@executor-js/react/components/spinner";
36
+ import { Textarea } from "@executor-js/react/components/textarea";
37
+ import {
38
+ emptyHttpCredentials,
39
+ httpCredentialsValid,
40
+ HttpCredentialsEditor,
41
+ serializeScopedHttpCredentials,
42
+ serializeHttpCredentials
43
+ } from "@executor-js/react/plugins/http-credentials";
44
+ import {
45
+ sourceDisplayNameFromUrl,
46
+ slugifyNamespace,
47
+ SourceIdentityFields,
48
+ useSourceIdentity
49
+ } from "@executor-js/react/plugins/source-identity";
50
+ import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets";
51
+ import {
52
+ oauthCallbackUrl,
53
+ oauthConnectionId,
54
+ useOAuthPopupFlow
55
+ } from "@executor-js/react/plugins/oauth-sign-in";
56
+ import {
57
+ CredentialControlField,
58
+ CredentialUsageRow,
59
+ useCredentialTargetScope
60
+ } from "@executor-js/react/plugins/credential-target-scope";
61
+ import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
62
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
63
+ var ErrorMessage = Schema.Struct({ message: Schema.String });
64
+ var decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
65
+ var errorMessageFromExit = (exit, fallback) => Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
66
+ onNone: () => fallback,
67
+ onSome: ({ message }) => message
68
+ });
69
+ function findPreset(id) {
70
+ if (!id) return void 0;
71
+ return mcpPresets.find((p) => p.id === id);
72
+ }
73
+ var init = { step: "url", url: "" };
74
+ function reducer(state, action) {
75
+ return Match.value(action).pipe(
76
+ Match.discriminator("type")("set-url", (a) => ({ step: "url", url: a.url })),
77
+ Match.discriminator("type")(
78
+ "probe-start",
79
+ () => ({
80
+ step: "probing",
81
+ url: state.url,
82
+ probe: "probe" in state ? state.probe : null
83
+ })
84
+ ),
85
+ Match.discriminator("type")(
86
+ "probe-ok",
87
+ (a) => ({ step: "probed", url: state.url, probe: a.probe })
88
+ ),
89
+ Match.discriminator("type")(
90
+ "probe-fail",
91
+ (a) => ({
92
+ step: "error",
93
+ url: state.url,
94
+ probe: null,
95
+ tokens: null,
96
+ error: a.error
97
+ })
98
+ ),
99
+ Match.discriminator("type")("oauth-start", () => {
100
+ if (state.step !== "probed" && state.step !== "error") return state;
101
+ return {
102
+ step: "oauth-starting",
103
+ url: state.url,
104
+ probe: state.step === "probed" ? state.probe : state.probe
105
+ };
106
+ }),
107
+ Match.discriminator("type")("oauth-waiting", (a) => {
108
+ if (state.step !== "oauth-starting") return state;
109
+ return {
110
+ step: "oauth-waiting",
111
+ url: state.url,
112
+ probe: state.probe,
113
+ sessionId: a.sessionId
114
+ };
115
+ }),
116
+ Match.discriminator("type")("oauth-ok", (a) => {
117
+ if (state.step !== "oauth-waiting") return state;
118
+ return {
119
+ step: "oauth-done",
120
+ url: state.url,
121
+ probe: state.probe,
122
+ tokens: a.tokens
123
+ };
124
+ }),
125
+ Match.discriminator("type")("oauth-fail", (a) => {
126
+ if (state.step !== "oauth-starting" && state.step !== "oauth-waiting") return state;
127
+ return {
128
+ step: "error",
129
+ url: state.url,
130
+ probe: state.probe,
131
+ tokens: null,
132
+ error: a.error
133
+ };
134
+ }),
135
+ Match.discriminator("type")("oauth-cancelled", () => {
136
+ if (state.step !== "oauth-waiting") return state;
137
+ return { step: "probed", url: state.url, probe: state.probe };
138
+ }),
139
+ Match.discriminator("type")("oauth-reset", () => {
140
+ if ("probe" in state && state.probe) {
141
+ return { step: "probed", url: state.url, probe: state.probe };
142
+ }
143
+ return state;
144
+ }),
145
+ Match.discriminator("type")("add-start", () => {
146
+ const tokens = state.step === "oauth-done" ? state.tokens : state.step === "probed" ? null : null;
147
+ const probe = "probe" in state ? state.probe : null;
148
+ if (!probe) return state;
149
+ return { step: "adding", url: state.url, probe, tokens };
150
+ }),
151
+ Match.discriminator("type")("add-fail", (a) => {
152
+ if (state.step !== "adding") return state;
153
+ return {
154
+ step: "error",
155
+ url: state.url,
156
+ probe: state.probe,
157
+ tokens: state.tokens,
158
+ error: a.error
159
+ };
160
+ }),
161
+ Match.discriminator("type")("retry", () => {
162
+ if (state.step !== "error") return state;
163
+ return state.probe ? state.tokens ? {
164
+ step: "oauth-done",
165
+ url: state.url,
166
+ probe: state.probe,
167
+ tokens: state.tokens
168
+ } : { step: "probed", url: state.url, probe: state.probe } : { step: "url", url: state.url };
169
+ }),
170
+ Match.exhaustive
171
+ );
172
+ }
173
+ function AddMcpSource(props) {
174
+ const allowStdio = props.allowStdio ?? false;
175
+ const rawPreset = findPreset(props.initialPreset);
176
+ const preset = rawPreset?.transport === "stdio" && !allowStdio ? void 0 : rawPreset;
177
+ const isStdioPreset = preset?.transport === "stdio";
178
+ const [transport, setTransport] = useState(
179
+ isStdioPreset && allowStdio ? "stdio" : "remote"
180
+ );
181
+ const [stdioCommand, setStdioCommand] = useState(isStdioPreset ? preset.command : "");
182
+ const [stdioArgs, setStdioArgs] = useState(
183
+ isStdioPreset && preset.args ? preset.args.join(" ") : ""
184
+ );
185
+ const [stdioEnv, setStdioEnv] = useState("");
186
+ const stdioIdentity = useSourceIdentity({
187
+ fallbackName: isStdioPreset ? preset.name : stdioCommand
188
+ });
189
+ const [stdioAdding, setStdioAdding] = useState(false);
190
+ const [stdioError, setStdioError] = useState(null);
191
+ const remoteUrl = !isStdioPreset && preset?.transport === void 0 && preset?.url ? preset.url : props.initialUrl ?? "";
192
+ const [state, dispatch] = useReducer(
193
+ reducer,
194
+ remoteUrl ? { step: "url", url: remoteUrl } : init
195
+ );
196
+ const scopeId = useScope();
197
+ const { credentialTargetScope: requestCredentialTargetScope } = useCredentialTargetScope();
198
+ const {
199
+ credentialTargetScope: oauthCredentialTargetScope,
200
+ setCredentialTargetScope: setOAuthCredentialTargetScope,
201
+ credentialScopeOptions
202
+ } = useCredentialTargetScope();
203
+ const doProbe = useAtomSet(probeMcpEndpoint, { mode: "promiseExit" });
204
+ const doAdd = useAtomSet(addMcpSourceOptimistic(scopeId), {
205
+ mode: "promiseExit"
206
+ });
207
+ const secretList = useSecretPickerSecrets();
208
+ const oauth = useOAuthPopupFlow({
209
+ popupName: "mcp-oauth",
210
+ popupBlockedMessage: "OAuth popup was blocked",
211
+ detectPopupClosed: false,
212
+ startErrorMessage: "Failed to start OAuth"
213
+ });
214
+ const [remoteAuthMode, setRemoteAuthMode] = useState("none");
215
+ const [remoteHeaders, setRemoteHeaders] = useState([]);
216
+ const [remoteCredentials, setRemoteCredentials] = useState(() => emptyHttpCredentials());
217
+ const probe = "probe" in state ? state.probe : null;
218
+ const tokens = "tokens" in state ? state.tokens : null;
219
+ const remoteIdentity = useSourceIdentity({
220
+ fallbackName: sourceDisplayNameFromUrl(state.url, "MCP") ?? probe?.serverName ?? probe?.name ?? ""
221
+ });
222
+ const isProbing = state.step === "probing";
223
+ const isAdding = state.step === "adding";
224
+ const isOAuthBusy = state.step === "oauth-starting" || state.step === "oauth-waiting" || oauth.busy;
225
+ const canUseNone = probe?.requiresOAuth !== true || probe.supportsDynamicRegistration === false;
226
+ const remoteHeadersComplete = remoteHeaders.every(
227
+ (header) => header.name.trim() && header.value.trim()
228
+ );
229
+ const remoteCredentialsComplete = httpCredentialsValid(remoteCredentials);
230
+ const authReady = remoteAuthMode === "none" ? canUseNone : tokens !== null;
231
+ const canAdd = Boolean(probe) && authReady && remoteHeadersComplete && remoteCredentialsComplete && !isAdding && !isOAuthBusy;
232
+ const probeError = state.step === "error" && state.probe === null ? state.error : null;
233
+ const otherError = state.step === "error" && state.probe !== null ? state.error : null;
234
+ const handleProbe = useCallback(async () => {
235
+ dispatch({ type: "probe-start" });
236
+ const { headers, queryParams } = serializeHttpCredentials(remoteCredentials);
237
+ const exit = await doProbe({
238
+ params: { scopeId },
239
+ payload: {
240
+ endpoint: state.url.trim(),
241
+ ...Object.keys(headers).length > 0 ? { headers } : {},
242
+ ...Object.keys(queryParams).length > 0 ? { queryParams } : {}
243
+ }
244
+ });
245
+ if (Exit.isFailure(exit)) {
246
+ dispatch({
247
+ type: "probe-fail",
248
+ error: errorMessageFromExit(exit, "Failed to connect")
249
+ });
250
+ return;
251
+ }
252
+ setRemoteAuthMode(exit.value.requiresOAuth ? "oauth2" : "none");
253
+ dispatch({ type: "probe-ok", probe: exit.value });
254
+ }, [state.url, scopeId, doProbe, remoteCredentials]);
255
+ const handleProbeRef = useRef(handleProbe);
256
+ handleProbeRef.current = handleProbe;
257
+ useEffect(() => {
258
+ if (transport !== "remote") return;
259
+ if (state.step !== "url") return;
260
+ const trimmed = state.url.trim();
261
+ if (!trimmed) return;
262
+ const handle = setTimeout(() => {
263
+ handleProbeRef.current();
264
+ }, 400);
265
+ return () => clearTimeout(handle);
266
+ }, [transport, state.step, state.url]);
267
+ const handleRemoteCredentialsChange = useCallback((next) => {
268
+ setRemoteCredentials(next);
269
+ }, []);
270
+ const handleOAuth = useCallback(async () => {
271
+ dispatch({ type: "oauth-start" });
272
+ const namespaceSlug = slugifyNamespace(remoteIdentity.namespace) || slugifyNamespace(probe?.namespace ?? "") || "mcp";
273
+ const { headers, queryParams } = serializeHttpCredentials(remoteCredentials);
274
+ await oauth.start({
275
+ payload: {
276
+ endpoint: state.url.trim(),
277
+ ...Object.keys(headers).length > 0 ? { headers } : {},
278
+ ...Object.keys(queryParams).length > 0 ? { queryParams } : {},
279
+ redirectUrl: oauthCallbackUrl(),
280
+ connectionId: oauthConnectionId({
281
+ pluginId: "mcp",
282
+ namespace: namespaceSlug
283
+ }),
284
+ tokenScope: oauthCredentialTargetScope,
285
+ strategy: { kind: "dynamic-dcr" },
286
+ pluginId: "mcp",
287
+ identityLabel: `${remoteIdentity.name.trim() || probe?.serverName || probe?.name || "MCP"} OAuth`
288
+ },
289
+ onSuccess: (result) => {
290
+ dispatch({
291
+ type: "oauth-ok",
292
+ tokens: {
293
+ connectionId: result.connectionId,
294
+ expiresAt: result.expiresAt,
295
+ scope: result.scope
296
+ }
297
+ });
298
+ },
299
+ onAuthorizationStarted: (result) => dispatch({ type: "oauth-waiting", sessionId: result.sessionId }),
300
+ onError: (error) => dispatch({ type: "oauth-fail", error })
301
+ });
302
+ }, [state.url, remoteIdentity, probe, remoteCredentials, oauth, oauthCredentialTargetScope]);
303
+ const handleCancelOAuth = useCallback(() => {
304
+ oauth.cancel();
305
+ dispatch({ type: "oauth-cancelled" });
306
+ }, [oauth]);
307
+ const handleAddRemote = useCallback(async () => {
308
+ if (!probe) return;
309
+ dispatch({ type: "add-start" });
310
+ const auth = remoteAuthMode === "oauth2" ? tokens ? {
311
+ kind: "oauth2",
312
+ connectionId: tokens.connectionId
313
+ } : {
314
+ kind: "oauth2",
315
+ connectionSlot: MCP_OAUTH_CONNECTION_SLOT
316
+ } : { kind: "none" };
317
+ const headers = Object.fromEntries(
318
+ remoteHeaders.map((header) => [header.name.trim(), header.value.trim()]).filter(([name, value2]) => name && value2)
319
+ );
320
+ const credentials = serializeScopedHttpCredentials(
321
+ remoteCredentials,
322
+ requestCredentialTargetScope
323
+ );
324
+ const remoteRequestHeaders = {
325
+ ...headers,
326
+ ...credentials.headers
327
+ };
328
+ const displayName = remoteIdentity.name.trim() || probe.serverName || probe.name;
329
+ const slugNamespace = slugifyNamespace(remoteIdentity.namespace);
330
+ const exit = await doAdd({
331
+ params: { scopeId },
332
+ payload: {
333
+ targetScope: scopeId,
334
+ transport: "remote",
335
+ name: displayName,
336
+ namespace: slugNamespace || void 0,
337
+ endpoint: state.url.trim(),
338
+ auth,
339
+ credentialTargetScope: remoteAuthMode === "oauth2" && tokens ? oauthCredentialTargetScope : requestCredentialTargetScope,
340
+ ...Object.keys(remoteRequestHeaders).length > 0 ? { headers: remoteRequestHeaders } : {},
341
+ ...Object.keys(credentials.queryParams).length > 0 ? { queryParams: credentials.queryParams } : {}
342
+ },
343
+ reactivityKeys: sourceWriteKeys
344
+ });
345
+ if (Exit.isFailure(exit)) {
346
+ dispatch({
347
+ type: "add-fail",
348
+ error: errorMessageFromExit(exit, "Failed to add source")
349
+ });
350
+ return;
351
+ }
352
+ props.onComplete();
353
+ }, [
354
+ probe,
355
+ remoteAuthMode,
356
+ remoteHeaders,
357
+ remoteCredentials,
358
+ remoteIdentity,
359
+ tokens,
360
+ state.url,
361
+ doAdd,
362
+ props,
363
+ scopeId,
364
+ requestCredentialTargetScope,
365
+ oauthCredentialTargetScope
366
+ ]);
367
+ const parseStdioArgs = (raw) => {
368
+ if (!raw.trim()) return [];
369
+ const args = [];
370
+ const regex = /[^\s"]+|"([^"]*)"/g;
371
+ let match2;
372
+ while ((match2 = regex.exec(raw)) !== null) {
373
+ args.push(match2[1] ?? match2[0]);
374
+ }
375
+ return args;
376
+ };
377
+ const parseStdioEnv = (raw) => {
378
+ if (!raw.trim()) return void 0;
379
+ const env = {};
380
+ for (const line of raw.split("\n")) {
381
+ const eq = line.indexOf("=");
382
+ if (eq > 0) {
383
+ env[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
384
+ }
385
+ }
386
+ return Object.keys(env).length > 0 ? env : void 0;
387
+ };
388
+ const handleAddStdio = useCallback(async () => {
389
+ const cmd = stdioCommand.trim();
390
+ if (!cmd) return;
391
+ setStdioAdding(true);
392
+ setStdioError(null);
393
+ const displayName = stdioIdentity.name.trim() || cmd;
394
+ const slugNamespace = slugifyNamespace(stdioIdentity.namespace);
395
+ const exit = await doAdd({
396
+ params: { scopeId },
397
+ payload: {
398
+ targetScope: scopeId,
399
+ transport: "stdio",
400
+ name: displayName,
401
+ namespace: slugNamespace || void 0,
402
+ command: cmd,
403
+ args: parseStdioArgs(stdioArgs),
404
+ env: parseStdioEnv(stdioEnv)
405
+ },
406
+ reactivityKeys: sourceWriteKeys
407
+ });
408
+ if (Exit.isFailure(exit)) {
409
+ setStdioError(errorMessageFromExit(exit, "Failed to add source"));
410
+ setStdioAdding(false);
411
+ return;
412
+ }
413
+ props.onComplete();
414
+ }, [stdioCommand, stdioArgs, stdioEnv, stdioIdentity, doAdd, scopeId, props]);
415
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-6", children: [
416
+ /* @__PURE__ */ jsxs("div", { children: [
417
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Add MCP Source" }),
418
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[13px] text-muted-foreground", children: "Connect to an MCP server to discover and use its tools." })
419
+ ] }),
420
+ allowStdio && /* @__PURE__ */ jsxs("div", { className: "flex gap-1 rounded-lg border border-border bg-muted/30 p-1", children: [
421
+ /* @__PURE__ */ jsx(
422
+ Button,
423
+ {
424
+ variant: "ghost",
425
+ type: "button",
426
+ onClick: () => setTransport("remote"),
427
+ className: `flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${transport === "remote" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
428
+ children: "Remote"
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsx(
432
+ Button,
433
+ {
434
+ variant: "ghost",
435
+ type: "button",
436
+ onClick: () => setTransport("stdio"),
437
+ className: `flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${transport === "stdio" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
438
+ children: "Stdio"
439
+ }
440
+ )
441
+ ] }),
442
+ transport === "remote" ? /* @__PURE__ */ jsxs(Fragment, { children: [
443
+ /* @__PURE__ */ jsx(
444
+ McpRemoteSourceFields,
445
+ {
446
+ url: state.url,
447
+ onUrlChange: (url) => dispatch({ type: "set-url", url }),
448
+ identity: remoteIdentity,
449
+ preview: probe,
450
+ probing: isProbing,
451
+ error: probeError,
452
+ onRetry: handleProbe
453
+ }
454
+ ),
455
+ /* @__PURE__ */ jsx(
456
+ HttpCredentialsEditor,
457
+ {
458
+ credentials: remoteCredentials,
459
+ onChange: handleRemoteCredentialsChange,
460
+ existingSecrets: secretList,
461
+ sourceName: remoteIdentity.name,
462
+ targetScope: requestCredentialTargetScope,
463
+ credentialScopeOptions,
464
+ bindingScopeOptions: credentialScopeOptions,
465
+ labels: {
466
+ headers: "Request headers",
467
+ queryParams: "Query parameters"
468
+ }
469
+ }
470
+ ),
471
+ probe && /* @__PURE__ */ jsxs("section", { className: "space-y-2.5", children: [
472
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
473
+ /* @__PURE__ */ jsx(FieldLabel, { children: "Authentication" }),
474
+ /* @__PURE__ */ jsx(
475
+ FilterTabs,
476
+ {
477
+ tabs: probe.requiresOAuth && probe.supportsDynamicRegistration ? [{ value: "oauth2", label: "OAuth" }] : [
478
+ { value: "none", label: "None" },
479
+ { value: "oauth2", label: "OAuth" }
480
+ ],
481
+ value: remoteAuthMode,
482
+ onChange: setRemoteAuthMode
483
+ }
484
+ )
485
+ ] }),
486
+ remoteAuthMode === "oauth2" && /* @__PURE__ */ jsx(
487
+ CredentialUsageRow,
488
+ {
489
+ value: oauthCredentialTargetScope,
490
+ options: credentialScopeOptions,
491
+ onChange: (targetScope) => {
492
+ setOAuthCredentialTargetScope(targetScope);
493
+ dispatch({ type: "oauth-reset" });
494
+ },
495
+ label: "Connection saved to",
496
+ help: "Choose who can use the OAuth connection.",
497
+ children: /* @__PURE__ */ jsxs(
498
+ CredentialControlField,
499
+ {
500
+ label: "Connect via OAuth",
501
+ help: "Start the provider OAuth flow.",
502
+ children: [
503
+ !tokens && state.step === "probed" && (probe.supportsDynamicRegistration ? /* @__PURE__ */ jsx(
504
+ Button,
505
+ {
506
+ type: "button",
507
+ onClick: handleOAuth,
508
+ variant: "outline",
509
+ className: "w-full",
510
+ children: "Sign in"
511
+ }
512
+ ) : /* @__PURE__ */ jsx("div", { className: "rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground", children: "This server requires OAuth, but its authorization server does not support dynamic client registration. Use request headers with a bearer token, or save the source and connect a supported OAuth connection later." })),
513
+ !tokens && state.step === "oauth-starting" && /* @__PURE__ */ jsxs("div", { className: "flex min-h-9 items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2", children: [
514
+ /* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }),
515
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Starting authorization..." })
516
+ ] }),
517
+ !tokens && state.step === "oauth-waiting" && /* @__PURE__ */ jsxs("div", { className: "flex min-h-9 items-center gap-2 rounded-md border border-blue-500/30 bg-blue-500/5 px-3 py-2", children: [
518
+ /* @__PURE__ */ jsx(Spinner, { className: "size-3.5 text-blue-500" }),
519
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-blue-600 dark:text-blue-400", children: "Waiting for authorization..." }),
520
+ /* @__PURE__ */ jsx(
521
+ Button,
522
+ {
523
+ type: "button",
524
+ variant: "ghost",
525
+ size: "sm",
526
+ onClick: handleCancelOAuth,
527
+ className: "ml-auto h-7 px-2 text-xs",
528
+ children: "Cancel"
529
+ }
530
+ )
531
+ ] }),
532
+ tokens && /* @__PURE__ */ jsxs("div", { className: "flex min-h-9 items-center gap-2 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-3 py-2", children: [
533
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", className: "size-3.5 text-emerald-500", children: /* @__PURE__ */ jsx(
534
+ "path",
535
+ {
536
+ d: "M3 8.5l3 3 7-7",
537
+ stroke: "currentColor",
538
+ strokeWidth: "1.5",
539
+ strokeLinecap: "round",
540
+ strokeLinejoin: "round"
541
+ }
542
+ ) }),
543
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-emerald-600 dark:text-emerald-400", children: "Authenticated" })
544
+ ] })
545
+ ]
546
+ }
547
+ )
548
+ }
549
+ )
550
+ ] }),
551
+ probe && /* @__PURE__ */ jsxs("section", { className: "space-y-2.5", children: [
552
+ /* @__PURE__ */ jsxs("div", { children: [
553
+ /* @__PURE__ */ jsx(Label, { children: "Additional headers" }),
554
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[12px] text-muted-foreground", children: "Plaintext headers sent with every request. Use request headers above for secret-backed values." })
555
+ ] }),
556
+ /* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsx(CardStackContent, { children: remoteHeaders.length === 0 ? /* @__PURE__ */ jsx(
557
+ AddPlainHeaderRow,
558
+ {
559
+ leading: /* @__PURE__ */ jsx("span", { children: "No headers" }),
560
+ onClick: () => setRemoteHeaders((headers) => [...headers, { name: "", value: "" }])
561
+ }
562
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
563
+ remoteHeaders.map((header, index) => /* @__PURE__ */ jsxs(CardStackEntry, { className: "flex-col items-stretch gap-2", children: [
564
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
565
+ /* @__PURE__ */ jsx(Label, { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: "Header" }),
566
+ /* @__PURE__ */ jsx(
567
+ Button,
568
+ {
569
+ type: "button",
570
+ variant: "ghost",
571
+ size: "xs",
572
+ className: "text-muted-foreground hover:text-destructive",
573
+ onClick: () => setRemoteHeaders(
574
+ (headers) => headers.filter((_, headerIndex) => headerIndex !== index)
575
+ ),
576
+ children: "Remove"
577
+ }
578
+ )
579
+ ] }),
580
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
581
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
582
+ /* @__PURE__ */ jsx(Label, { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: "Name" }),
583
+ /* @__PURE__ */ jsx(
584
+ Input,
585
+ {
586
+ value: header.name,
587
+ onChange: (event) => setRemoteHeaders(
588
+ (headers) => headers.map(
589
+ (current, headerIndex) => headerIndex === index ? {
590
+ ...current,
591
+ name: event.target.value
592
+ } : current
593
+ )
594
+ ),
595
+ placeholder: "X-Organization-Id",
596
+ className: "h-8 text-xs font-mono"
597
+ }
598
+ )
599
+ ] }),
600
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
601
+ /* @__PURE__ */ jsx(Label, { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: "Value" }),
602
+ /* @__PURE__ */ jsx(
603
+ Input,
604
+ {
605
+ value: header.value,
606
+ onChange: (event) => setRemoteHeaders(
607
+ (headers) => headers.map(
608
+ (current, headerIndex) => headerIndex === index ? {
609
+ ...current,
610
+ value: event.target.value
611
+ } : current
612
+ )
613
+ ),
614
+ placeholder: "workspace-id",
615
+ className: "h-8 text-xs font-mono"
616
+ }
617
+ )
618
+ ] })
619
+ ] })
620
+ ] }, index)),
621
+ /* @__PURE__ */ jsx(
622
+ AddPlainHeaderRow,
623
+ {
624
+ onClick: () => setRemoteHeaders((headers) => [...headers, { name: "", value: "" }])
625
+ }
626
+ )
627
+ ] }) }) })
628
+ ] }),
629
+ otherError && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
630
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-[12px] text-destructive", children: otherError }) }),
631
+ /* @__PURE__ */ jsx(
632
+ Button,
633
+ {
634
+ type: "button",
635
+ variant: "outline",
636
+ size: "sm",
637
+ onClick: () => dispatch({ type: "retry" }),
638
+ className: "text-xs",
639
+ children: "Try again"
640
+ }
641
+ )
642
+ ] }),
643
+ /* @__PURE__ */ jsxs(FloatActions, { children: [
644
+ /* @__PURE__ */ jsx(
645
+ Button,
646
+ {
647
+ type: "button",
648
+ variant: "ghost",
649
+ onClick: () => {
650
+ oauth.cancel();
651
+ props.onCancel();
652
+ },
653
+ disabled: isAdding,
654
+ children: "Cancel"
655
+ }
656
+ ),
657
+ (probe || isProbing) && /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleAddRemote, disabled: !canAdd, children: isAdding ? /* @__PURE__ */ jsxs(Fragment, { children: [
658
+ /* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }),
659
+ " Adding\u2026"
660
+ ] }) : "Add source" })
661
+ ] })
662
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
663
+ /* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsxs(CardStackContent, { className: "border-t-0", children: [
664
+ /* @__PURE__ */ jsx(
665
+ CardStackEntryField,
666
+ {
667
+ label: "Command",
668
+ description: "- The executable to run (e.g. npx, uvx, node).",
669
+ children: /* @__PURE__ */ jsx(
670
+ Input,
671
+ {
672
+ value: stdioCommand,
673
+ onChange: (e) => setStdioCommand(e.target.value),
674
+ placeholder: "npx",
675
+ className: "font-mono text-sm"
676
+ }
677
+ )
678
+ }
679
+ ),
680
+ /* @__PURE__ */ jsx(
681
+ CardStackEntryField,
682
+ {
683
+ label: "Arguments",
684
+ description: "- Space-separated arguments passed to the command.",
685
+ children: /* @__PURE__ */ jsx(
686
+ Input,
687
+ {
688
+ value: stdioArgs,
689
+ onChange: (e) => setStdioArgs(e.target.value),
690
+ placeholder: "-y chrome-devtools-mcp@latest",
691
+ className: "font-mono text-sm"
692
+ }
693
+ )
694
+ }
695
+ ),
696
+ /* @__PURE__ */ jsx(
697
+ CardStackEntryField,
698
+ {
699
+ label: "Environment variables",
700
+ description: "- One per line, KEY=value format.",
701
+ children: /* @__PURE__ */ jsx(
702
+ Textarea,
703
+ {
704
+ value: stdioEnv,
705
+ onChange: (e) => setStdioEnv(e.target.value),
706
+ placeholder: "KEY=value\nANOTHER=value",
707
+ rows: 3,
708
+ maxRows: 10,
709
+ className: "font-mono text-sm"
710
+ }
711
+ )
712
+ }
713
+ )
714
+ ] }) }),
715
+ /* @__PURE__ */ jsx(SourceIdentityFields, { identity: stdioIdentity, namePlaceholder: "My MCP Server" }),
716
+ stdioError && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-[12px] text-destructive", children: stdioError }) }),
717
+ /* @__PURE__ */ jsxs(FloatActions, { children: [
718
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: props.onCancel, disabled: stdioAdding, children: "Cancel" }),
719
+ /* @__PURE__ */ jsx(
720
+ Button,
721
+ {
722
+ type: "button",
723
+ onClick: handleAddStdio,
724
+ disabled: !stdioCommand.trim() || stdioAdding,
725
+ children: stdioAdding ? /* @__PURE__ */ jsxs(Fragment, { children: [
726
+ /* @__PURE__ */ jsx(Spinner, { className: "size-3.5" }),
727
+ " Adding\u2026"
728
+ ] }) : "Add source"
729
+ }
730
+ )
731
+ ] })
732
+ ] })
733
+ ] });
734
+ }
735
+ function AddPlainHeaderRow({
736
+ onClick,
737
+ leading
738
+ }) {
739
+ return (
740
+ // oxlint-disable-next-line react/forbid-elements
741
+ /* @__PURE__ */ jsxs(
742
+ "button",
743
+ {
744
+ type: "button",
745
+ onClick: (event) => {
746
+ event.stopPropagation();
747
+ onClick();
748
+ },
749
+ "aria-label": "Add header",
750
+ className: "flex w-full items-center justify-between gap-4 px-4 py-3 text-sm text-muted-foreground outline-none transition-[background-color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-accent/40 focus-visible:bg-accent/40",
751
+ children: [
752
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 text-left", children: leading }),
753
+ /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 16 16", fill: "none", className: "size-4 shrink-0", children: /* @__PURE__ */ jsx("path", { d: "M8 3.5v9M3.5 8h9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
754
+ ]
755
+ }
756
+ )
757
+ );
758
+ }
759
+ export {
760
+ AddMcpSource as default
761
+ };
762
+ //# sourceMappingURL=AddMcpSource-VM3HY26S.js.map