@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 +39 -15
- package/dist/index.cjs +174 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +173 -102
- package/dist/index.js.map +1 -1
- package/dist/{singul-GZKBHJNI.css → shuffle-mcp-XZEVQXW2.css} +89 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="https://
|
|
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://
|
|
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://
|
|
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/
|
|
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 '{
|
|
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 {
|
|
72
|
+
import { ShuffleMCP } from 'shuffle-mcps';
|
|
65
73
|
|
|
66
74
|
export default function App() {
|
|
67
75
|
return (
|
|
68
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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<
|
|
157
|
-
<
|
|
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
|
-
|
|
33
|
-
default: () =>
|
|
32
|
+
ShuffleMCP: () => ShuffleMCP,
|
|
33
|
+
default: () => ShuffleMCP_default
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// ShuffleMCP.tsx
|
|
38
38
|
var import_react = __toESM(require("react"), 1);
|
|
39
39
|
var import_algoliasearch = require("algoliasearch");
|
|
40
|
-
var
|
|
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
|
|
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: {
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
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.
|
|
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:
|
|
423
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "singul-search-
|
|
424
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
425
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
"
|
|
455
|
-
{
|
|
456
|
-
|
|
457
|
-
role: "
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
514
|
-
var
|
|
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
|
-
|
|
588
|
+
ShuffleMCP
|
|
518
589
|
});
|
|
519
590
|
//# sourceMappingURL=index.cjs.map
|