@shuffleio/shuffle-mcps 0.0.2 → 0.0.4

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://unpkg.com/@shuffleio/shuffle-mcps/docs/agent-icon.png" alt="Shuffle MCPs" width="96" />
3
+ <img src="https://security.shuffler.io/mcp/agent-icon.png" alt="Shuffle MCPs" width="96" />
4
4
 
5
5
  # Shuffle MCPs
6
6
 
@@ -17,11 +17,11 @@ Turn 3,000+ SaaS tools into MCP servers your agents can use. One search, one cli
17
17
  <table align="center">
18
18
  <tr>
19
19
  <td align="center" width="50%">
20
- <img src="https://unpkg.com/@shuffleio/shuffle-mcps/docs/search-preview.png" alt="Search 3,000+ integrations" width="380" style="border-radius: 12px;" />
20
+ <img src="https://security.shuffler.io/mcp/search-preview.png" alt="Search 3,000+ integrations" width="380" style="border-radius: 12px;" />
21
21
  <br/><sub><b>1. Search any of 3,000+ tools</b></sub>
22
22
  </td>
23
23
  <td align="center" width="50%">
24
- <img src="https://unpkg.com/@shuffleio/shuffle-mcps/docs/app-detail-preview.png" alt="Authenticate and use as MCP" width="380" style="border-radius: 12px;" />
24
+ <img src="https://security.shuffler.io/mcp/app-detail-preview.png" alt="Authenticate and use as MCP" width="380" style="border-radius: 12px;" />
25
25
  <br/><sub><b>2. Authenticate — your agent uses it instantly</b></sub>
26
26
  </td>
27
27
  </tr>
@@ -38,10 +38,18 @@ Turn 3,000+ SaaS tools into MCP servers your agents can use. One search, one cli
38
38
  Authenticate any tool at [shuffler.io](https://shuffler.io), grab your API key, and call it:
39
39
 
40
40
  ```bash
41
- curl https://shuffler.io/api/v1/apps/YOUR_APP/run \
41
+ curl https://shuffler.io/api/v1/apps/YOUR_APP/mcp \
42
42
  -H "Authorization: Bearer YOUR_API_KEY" \
43
43
  -H "Content-Type: application/json" \
44
- -d '{"action": "send_message", "fields": {"channel": "general", "text": "hi"}}'
44
+ -d '{
45
+ "jsonrpc": "2.0",
46
+ "id": "1",
47
+ "method": "tools/call",
48
+ "params": {
49
+ "tool_name": "YOUR_APP",
50
+ "input": { "text": "send a hi message to #general on slack" }
51
+ }
52
+ }'
45
53
  ```
46
54
 
47
55
  That is the MCP. Any agent, any language, no install. Full API reference: [shuffler.io/docs/API](https://shuffler.io/docs/API).
@@ -61,11 +69,11 @@ Peer deps: `react >= 18`, `react-dom >= 18`, `algoliasearch >= 5`.
61
69
  ## Quick start
62
70
 
63
71
  ```tsx
64
- import { SingulJS } from 'shuffle-mcps';
72
+ import { ShuffleMCP } from 'shuffle-mcps';
65
73
 
66
74
  export default function App() {
67
75
  return (
68
- <SingulJS
76
+ <ShuffleMCP
69
77
  authToken="any-token"
70
78
  inline
71
79
  layout="grid"
@@ -83,7 +91,7 @@ export default function App() {
83
91
  The most common pattern. Set `preventDefault` and handle `onAppSelected` to chain into your own auth or detail UI:
84
92
 
85
93
  ```tsx
86
- <SingulJS
94
+ <ShuffleMCP
87
95
  authToken={token}
88
96
  inline
89
97
  preventDefault
@@ -96,7 +104,7 @@ Full two-drawer reference implementation: [`src/components/shared/AppSearchDrawe
96
104
  ### Predefined search
97
105
 
98
106
  ```tsx
99
- <SingulJS
107
+ <ShuffleMCP
100
108
  authToken="..."
101
109
  inline
102
110
  initialFilterQuery="siem"
@@ -109,7 +117,7 @@ Full two-drawer reference implementation: [`src/components/shared/AppSearchDrawe
109
117
  ```tsx
110
118
  const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
111
119
 
112
- <SingulJS
120
+ <ShuffleMCP
113
121
  authToken="..."
114
122
  inline
115
123
  multiSelect
@@ -122,7 +130,7 @@ const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
122
130
  ### Your own backend
123
131
 
124
132
  ```tsx
125
- <SingulJS
133
+ <ShuffleMCP
126
134
  authToken={user.token}
127
135
  apiKey={user.apiKey}
128
136
  apiBaseUrl="https://your-backend.example.com"
@@ -138,23 +146,39 @@ When `apiKey` is set, the component also fetches the user's **private apps** fro
138
146
  | Prop | Type | Description |
139
147
  |---|---|---|
140
148
  | `authToken` | `string` | Required. Forwarded into the auth URL. |
149
+ | `orgId` | `string` | Optional Shuffle organization ID. When set, every API call (`/api/v1/apps/authentication`, `/api/v1/apps`) is sent with an `Org-Id: <orgId>` header, and the auth URL gets `&org_id=<orgId>` appended. Default: not sent. |
141
150
  | `inline` | `boolean` | Inline results vs floating dropdown. |
142
151
  | `layout` | `'list' \| 'grid'` | Result layout. Default `'list'`. |
143
152
  | `initialFilterQuery` | `string` | Pre-filter without filling the input. |
144
- | `preventDefault` | `boolean` | Skip default `window.open(authUrl)` so you can handle selection. |
145
- | `onAppSelected` | `(detail) => void` | Fires on single-select pick. |
153
+ | `preventDefault` | `boolean` | Skip the default click behavior (built-in drawer or `window.open(authUrl)`) so you can handle selection yourself. |
154
+ | `onAppSelected` | `(detail) => void` | Fires on single-select pick. If provided, the built-in drawer is **not** opened — you take over. |
146
155
  | `multiSelect` | `boolean` | Allow selecting multiple apps. |
147
156
  | `apiKey` | `string` | Bearer token. Enables status dots and merges your private apps from `/api/v1/apps`. |
148
157
  | `showSourceFilter` | `boolean` | Toggle the All / Public / Private filter. Default `true`. |
149
158
  | `customStyles` | `CustomStyles` | Per-slot style overrides. |
150
159
 
160
+ ### Default click behavior
161
+
162
+ When you do **not** pass `onAppSelected`, clicking an app opens a built-in right-side drawer that shows the app, its existing authentication entries (with Configured / Tested / Inactive chips), and a CTA that opens the Shuffle auth flow in a new tab. Pass `onAppSelected` to take over, or `preventDefault` to disable both.
163
+
164
+ ### Org scoping
165
+
166
+ ```tsx
167
+ <ShuffleMCP
168
+ authToken={token}
169
+ apiKey={user.apiKey}
170
+ orgId={currentOrg.id} // every request now scoped to this org
171
+ />
172
+ ```
173
+
174
+
151
175
  Full prop reference, framework setup (Next.js, Vue), styling slots, custom rendering, and publishing: [**LIBRARY.md**](./LIBRARY.md).
152
176
 
153
177
  ## Imperative handle
154
178
 
155
179
  ```tsx
156
- const ref = useRef<SingulJSHandle>(null);
157
- <SingulJS ref={ref} authToken="..." inline />
180
+ const ref = useRef<ShuffleMCPHandle>(null);
181
+ <ShuffleMCP ref={ref} authToken="..." inline />
158
182
 
159
183
  ref.current?.search('slack');
160
184
  ref.current?.clear();
package/dist/index.cjs CHANGED
@@ -29,21 +29,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // index.ts
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
- SingulJS: () => SingulJS,
33
- default: () => SingulJS_default
32
+ ShuffleMCP: () => ShuffleMCP,
33
+ default: () => ShuffleMCP_default
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
- // SingulJS.tsx
37
+ // ShuffleMCP.tsx
38
38
  var import_react = __toESM(require("react"), 1);
39
39
  var import_algoliasearch = require("algoliasearch");
40
- var import_singul = require("./singul-GZKBHJNI.css");
40
+ var import_shuffle_mcp = require("./shuffle-mcp-XZEVQXW2.css");
41
41
  var import_jsx_runtime = require("react/jsx-runtime");
42
42
  var DEFAULT_ALGOLIA_APP_ID = "JNSS5CFDZZ";
43
43
  var DEFAULT_ALGOLIA_API_KEY = "33e4e3564f4f060e96e0531957bed552";
44
44
  var DEFAULT_ALGOLIA_INDEX = "appsearch";
45
- var SingulJS = import_react.default.forwardRef(({
45
+ var ShuffleMCP = import_react.default.forwardRef(({
46
46
  authToken,
47
+ orgId,
47
48
  placeholder = "Search apps...",
48
49
  layout = "list",
49
50
  gridColumns = 3,
@@ -89,6 +90,7 @@ var SingulJS = import_react.default.forwardRef(({
89
90
  const [selectedIndex, setSelectedIndex] = (0, import_react.useState)(-1);
90
91
  const [internalSelectedApps, setInternalSelectedApps] = (0, import_react.useState)(selectedApps);
91
92
  const [authenticatedApps, setAuthenticatedApps] = (0, import_react.useState)(externalAuthenticatedApps || []);
93
+ const [drawerApp, setDrawerApp] = (0, import_react.useState)(null);
92
94
  const hasInitialized = (0, import_react.useRef)(false);
93
95
  const containerRef = (0, import_react.useRef)(null);
94
96
  const inputRef = (0, import_react.useRef)(null);
@@ -100,7 +102,8 @@ var SingulJS = import_react.default.forwardRef(({
100
102
  try {
101
103
  const response = await fetch(`${apiBaseUrl}${authPath}`, {
102
104
  headers: {
103
- "Authorization": `Bearer ${apiKey}`
105
+ "Authorization": `Bearer ${apiKey}`,
106
+ ...orgId ? { "Org-Id": orgId } : {}
104
107
  }
105
108
  });
106
109
  if (response.ok) {
@@ -117,7 +120,7 @@ var SingulJS = import_react.default.forwardRef(({
117
120
  };
118
121
  fetchAuthenticatedApps();
119
122
  }
120
- }, [apiKey, apiBaseUrl]);
123
+ }, [apiKey, apiBaseUrl, orgId]);
121
124
  (0, import_react.useEffect)(() => {
122
125
  if (!apiKey || disablePrivateApps) {
123
126
  setPrivateApps([]);
@@ -126,7 +129,10 @@ var SingulJS = import_react.default.forwardRef(({
126
129
  const fetchPrivateApps = async () => {
127
130
  try {
128
131
  const response = await fetch(`${apiBaseUrl}${privateAppsPath}`, {
129
- headers: { "Authorization": `Bearer ${apiKey}` },
132
+ headers: {
133
+ "Authorization": `Bearer ${apiKey}`,
134
+ ...orgId ? { "Org-Id": orgId } : {}
135
+ },
130
136
  credentials: "include"
131
137
  });
132
138
  if (!response.ok) return;
@@ -158,7 +164,7 @@ var SingulJS = import_react.default.forwardRef(({
158
164
  }
159
165
  };
160
166
  fetchPrivateApps();
161
- }, [apiKey, apiBaseUrl, privateAppsPath, disablePrivateApps]);
167
+ }, [apiKey, apiBaseUrl, privateAppsPath, disablePrivateApps, orgId]);
162
168
  (0, import_react.useEffect)(() => {
163
169
  if (authenticatedApps.length > 0 && results.length > 0) {
164
170
  const validatedApps = authenticatedApps.filter((auth) => auth.validation?.valid);
@@ -277,22 +283,24 @@ var SingulJS = import_react.default.forwardRef(({
277
283
  }, 300);
278
284
  }, [performSearch, onSearchChange]);
279
285
  const selectApp = (0, import_react.useCallback)((app) => {
280
- const authUrl = `${apiBaseUrl}${appAuthPath}?app_id=${app.objectID}&auth=${authToken}&source=shuffle`;
286
+ const authUrl = `${apiBaseUrl}${appAuthPath}?app_id=${app.objectID}&auth=${authToken}&source=shuffle${orgId ? `&org_id=${encodeURIComponent(orgId)}` : ""}`;
281
287
  if (multiSelect) {
282
288
  const isAlreadySelected = internalSelectedApps.some((a) => a.objectID === app.objectID);
283
289
  const newSelection = isAlreadySelected ? internalSelectedApps.filter((a) => a.objectID !== app.objectID) : [...internalSelectedApps, app];
284
290
  setInternalSelectedApps(newSelection);
285
291
  onSelectionChange?.(newSelection);
286
292
  } else {
287
- onAppSelected?.({ app, authUrl });
288
- if (!preventDefault) {
289
- window.open(authUrl, "_blank");
293
+ if (onAppSelected) {
294
+ onAppSelected({ app, authUrl });
295
+ if (!preventDefault) {
296
+ window.open(authUrl, "_blank");
297
+ }
298
+ } else if (!preventDefault) {
299
+ setDrawerApp(app);
290
300
  }
291
301
  setIsOpen(false);
292
- setQuery("");
293
- setResults([]);
294
302
  }
295
- }, [authToken, apiBaseUrl, appAuthPath, multiSelect, internalSelectedApps, onAppSelected, onSelectionChange, preventDefault]);
303
+ }, [authToken, apiBaseUrl, appAuthPath, orgId, multiSelect, internalSelectedApps, onAppSelected, onSelectionChange, preventDefault]);
296
304
  const handleKeyDown = (0, import_react.useCallback)((event) => {
297
305
  if (!isOpen) return;
298
306
  switch (event.key) {
@@ -413,107 +421,170 @@ var SingulJS = import_react.default.forwardRef(({
413
421
  app.objectID
414
422
  );
415
423
  };
416
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
424
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
417
425
  "div",
418
426
  {
419
427
  ref: containerRef,
420
428
  className: `singul-container ${inline ? "singul-inline" : ""} ${className}`,
421
429
  style: customStyles.container,
422
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-search-bar-container", children: [
423
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-search-input-wrapper", style: customStyles.inputWrapper, children: [
424
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
425
- "input",
430
+ children: [
431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-search-bar-container", children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-search-input-wrapper", style: customStyles.inputWrapper, children: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
434
+ "input",
435
+ {
436
+ ref: inputRef,
437
+ type: "text",
438
+ className: "singul-search-input",
439
+ style: customStyles.input,
440
+ placeholder,
441
+ value: query,
442
+ onChange: handleInputChange,
443
+ onFocus: () => {
444
+ if (!inline && query.trim() && results.length > 0) {
445
+ setIsOpen(true);
446
+ }
447
+ },
448
+ onKeyDown: handleKeyDown,
449
+ autoComplete: "off"
450
+ }
451
+ ),
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-search-icon", style: customStyles.searchIcon, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 21-4.35-4.35" })
455
+ ] }) }),
456
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-loading-spinner", style: customStyles.loadingSpinner, children: renderLoadingState ? renderLoadingState() : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-spinner", style: customStyles.spinner }) })
457
+ ] }),
458
+ showSourceFilter && privateApps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-source-filter", role: "tablist", "aria-label": "Filter by app source", children: [
459
+ { key: "all", label: "All", count: results.length + filteredPrivateApps.length },
460
+ { key: "public", label: "Public", count: results.length },
461
+ { key: "private", label: "Private", count: filteredPrivateApps.length }
462
+ ].map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
463
+ "button",
464
+ {
465
+ type: "button",
466
+ role: "tab",
467
+ "aria-selected": sourceFilter === opt.key,
468
+ className: `singul-source-pill ${sourceFilter === opt.key ? "singul-source-pill-active" : ""}`,
469
+ onClick: () => setSourceFilter(opt.key),
470
+ children: [
471
+ opt.label,
472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "singul-source-count", children: opt.count })
473
+ ]
474
+ },
475
+ opt.key
476
+ )) }),
477
+ inline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
478
+ "div",
426
479
  {
427
- ref: inputRef,
428
- type: "text",
429
- className: "singul-search-input",
430
- style: customStyles.input,
431
- placeholder,
432
- value: query,
433
- onChange: handleInputChange,
434
- onFocus: () => {
435
- if (!inline && query.trim() && results.length > 0) {
436
- setIsOpen(true);
437
- }
480
+ className: `singul-results-container ${layout === "grid" ? "singul-results-grid" : ""}`,
481
+ style: {
482
+ ...customStyles.resultsContainer,
483
+ ...getGridColumnsStyle,
484
+ ...layout === "grid" ? { display: "grid" } : {}
438
485
  },
439
- onKeyDown: handleKeyDown,
440
- autoComplete: "off"
486
+ children: displayResults.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
487
+ displayResults.map((app, index) => renderAppItem(app, index)),
488
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-end-of-results", style: {
489
+ padding: "10px 16px",
490
+ color: "rgba(255, 255, 255, 0.3)",
491
+ textAlign: "center",
492
+ fontSize: "11px",
493
+ gridColumn: "1 / -1"
494
+ }, children: "Can't find what you're looking for? Try a different search term." })
495
+ ] }) : query.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
496
+ 'No integrations match "',
497
+ query,
498
+ '". Try searching for a different app or category.'
499
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: "Start typing to search integrations..." })
441
500
  }
442
501
  ),
443
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-search-icon", style: customStyles.searchIcon, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
444
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
445
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 21-4.35-4.35" })
446
- ] }) }),
447
- isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-loading-spinner", style: customStyles.loadingSpinner, children: renderLoadingState ? renderLoadingState() : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-spinner", style: customStyles.spinner }) })
502
+ !inline && isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
503
+ "div",
504
+ {
505
+ className: `singul-dropdown ${layout === "grid" ? "singul-dropdown-grid" : ""}`,
506
+ style: {
507
+ ...customStyles.dropdown,
508
+ ...getGridColumnsStyle,
509
+ ...layout === "grid" ? { display: "grid" } : {}
510
+ },
511
+ children: displayResults.length > 0 ? displayResults.map((app, index) => renderAppItem(app, index)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
512
+ 'No apps found for "',
513
+ query,
514
+ '"'
515
+ ] }) })
516
+ }
517
+ )
448
518
  ] }),
449
- showSourceFilter && privateApps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-source-filter", role: "tablist", "aria-label": "Filter by app source", children: [
450
- { key: "all", label: "All", count: results.length + filteredPrivateApps.length },
451
- { key: "public", label: "Public", count: results.length },
452
- { key: "private", label: "Private", count: filteredPrivateApps.length }
453
- ].map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
454
- "button",
455
- {
456
- type: "button",
457
- role: "tab",
458
- "aria-selected": sourceFilter === opt.key,
459
- className: `singul-source-pill ${sourceFilter === opt.key ? "singul-source-pill-active" : ""}`,
460
- onClick: () => setSourceFilter(opt.key),
461
- children: [
462
- opt.label,
463
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "singul-source-count", children: opt.count })
464
- ]
465
- },
466
- opt.key
467
- )) }),
468
- inline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
469
- "div",
470
- {
471
- className: `singul-results-container ${layout === "grid" ? "singul-results-grid" : ""}`,
472
- style: {
473
- ...customStyles.resultsContainer,
474
- ...getGridColumnsStyle,
475
- ...layout === "grid" ? { display: "grid" } : {}
476
- },
477
- children: displayResults.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
478
- displayResults.map((app, index) => renderAppItem(app, index)),
479
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-end-of-results", style: {
480
- padding: "10px 16px",
481
- color: "rgba(255, 255, 255, 0.3)",
482
- textAlign: "center",
483
- fontSize: "11px",
484
- gridColumn: "1 / -1"
485
- }, children: "Can't find what you're looking for? Try a different search term." })
486
- ] }) : query.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
487
- 'No integrations match "',
488
- query,
489
- '". Try searching for a different app or category.'
490
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: "Start typing to search integrations..." })
491
- }
492
- ),
493
- !inline && isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
494
- "div",
495
- {
496
- className: `singul-dropdown ${layout === "grid" ? "singul-dropdown-grid" : ""}`,
497
- style: {
498
- ...customStyles.dropdown,
499
- ...getGridColumnsStyle,
500
- ...layout === "grid" ? { display: "grid" } : {}
501
- },
502
- children: displayResults.length > 0 ? displayResults.map((app, index) => renderAppItem(app, index)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-empty-state", style: customStyles.emptyState, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
503
- 'No apps found for "',
504
- query,
505
- '"'
506
- ] }) })
507
- }
508
- )
509
- ] })
519
+ drawerApp && (() => {
520
+ const norm = (n) => (n || "").toLowerCase().replace(/[\s_\-]+/g, "_");
521
+ const matchingAuths = authenticatedApps.filter(
522
+ (a) => norm(a.app?.name || "") === norm(drawerApp.name)
523
+ );
524
+ const drawerAuthUrl = `${apiBaseUrl}${appAuthPath}?app_id=${drawerApp.objectID}&auth=${authToken}&source=shuffle${orgId ? `&org_id=${encodeURIComponent(orgId)}` : ""}`;
525
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-drawer-backdrop", onClick: () => setDrawerApp(null) }),
527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "singul-drawer", role: "dialog", "aria-label": `${drawerApp.name} configuration`, children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "singul-drawer-header", children: [
529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-drawer-title-row", children: [
530
+ drawerApp.image_url && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: drawerApp.image_url, alt: drawerApp.name, className: "singul-drawer-icon" }),
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-drawer-title", children: drawerApp.name.replace(/_/g, " ") }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-drawer-subtitle", children: "App configuration" })
534
+ ] })
535
+ ] }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
537
+ "button",
538
+ {
539
+ type: "button",
540
+ className: "singul-drawer-close",
541
+ onClick: () => setDrawerApp(null),
542
+ "aria-label": "Close",
543
+ children: "\xD7"
544
+ }
545
+ )
546
+ ] }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-drawer-body", children: [
548
+ drawerApp.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "singul-drawer-desc", children: drawerApp.description }),
549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-drawer-section-title", children: [
550
+ "Authentication",
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "singul-drawer-count", children: [
552
+ matchingAuths.length,
553
+ " configuration",
554
+ matchingAuths.length === 1 ? "" : "s",
555
+ " found"
556
+ ] })
557
+ ] }),
558
+ matchingAuths.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "singul-drawer-auth-list", children: matchingAuths.map((a) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "singul-drawer-auth-item", children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-drawer-auth-label", children: a.label || "Untitled" }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-drawer-auth-chips", children: [
561
+ a.active && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "singul-chip singul-chip-configured", children: "Configured" }),
562
+ a.validation?.valid && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "singul-chip singul-chip-tested", children: "Tested" }),
563
+ !a.active && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "singul-chip singul-chip-inactive", children: "Inactive" })
564
+ ] })
565
+ ] }, a.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "singul-drawer-empty", children: "No authentications yet for this app." }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
567
+ "a",
568
+ {
569
+ href: drawerAuthUrl,
570
+ target: "_blank",
571
+ rel: "noopener noreferrer",
572
+ className: "singul-drawer-cta",
573
+ children: matchingAuths.length > 0 ? "Manage authentication" : "Add authentication"
574
+ }
575
+ )
576
+ ] })
577
+ ] })
578
+ ] });
579
+ })()
580
+ ]
510
581
  }
511
582
  );
512
583
  });
513
- SingulJS.displayName = "SingulJS";
514
- var SingulJS_default = SingulJS;
584
+ ShuffleMCP.displayName = "ShuffleMCP";
585
+ var ShuffleMCP_default = ShuffleMCP;
515
586
  // Annotate the CommonJS export names for ESM import in node:
516
587
  0 && (module.exports = {
517
- SingulJS
588
+ ShuffleMCP
518
589
  });
519
590
  //# sourceMappingURL=index.cjs.map