@shuffleio/shuffle-mcps 0.0.3 → 0.0.5
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 +170 -17
- package/dist/index.cjs +5332 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -5
- package/dist/index.d.ts +341 -5
- package/dist/index.js +5364 -169
- package/dist/index.js.map +1 -1
- package/dist/{singul-GZKBHJNI.css → shuffle-mcp-JQWKQNUP.css} +357 -21
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -8,7 +8,6 @@ Turn 3,000+ SaaS tools into MCP servers your agents can use. One search, one cli
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@shuffleio/shuffle-mcps)
|
|
10
10
|
[](https://www.npmjs.com/package/@shuffleio/shuffle-mcps)
|
|
11
|
-
[](https://github.com/Shuffle/singul.js)
|
|
12
11
|
|
|
13
12
|
</div>
|
|
14
13
|
|
|
@@ -29,7 +28,7 @@ Turn 3,000+ SaaS tools into MCP servers your agents can use. One search, one cli
|
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
> **See it live:** [security.shuffler.io/
|
|
31
|
+
> **See it live:** [security.shuffler.io/shuffle-mcp-demo](https://security.shuffler.io/shuffle-mcp-demo) — a fully working demo of every component in this package (inline search, search drawer, app detail drawer, auth, Try MCP, and Try individual actions). No login required.
|
|
33
32
|
|
|
34
33
|
> **No SDK required.** Once a tool is authenticated in Shuffle, it is live as an MCP server immediately — call it from any agent over plain HTTP. This npm package is the optional UI layer for embedding the search-and-connect flow inside your own product.
|
|
35
34
|
|
|
@@ -74,7 +73,7 @@ import { ShuffleMCP } from 'shuffle-mcps';
|
|
|
74
73
|
export default function App() {
|
|
75
74
|
return (
|
|
76
75
|
<ShuffleMCP
|
|
77
|
-
|
|
76
|
+
apiKey="YOUR_SHUFFLE_API_KEY"
|
|
78
77
|
inline
|
|
79
78
|
layout="grid"
|
|
80
79
|
gridColumns={3}
|
|
@@ -84,28 +83,122 @@ export default function App() {
|
|
|
84
83
|
}
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
##
|
|
86
|
+
## The three components
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
The library exports three React components — use one, two, or all three. They share state, so you can mix-and-match.
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
| Component | What it does | Live demo |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| [`<ShuffleMCP />`](#1-shufflemcp--inline-search) | Inline search box that merges Algolia's 3,000+ public app catalog with the current user's private apps | [/shuffle-mcp-demo §1](https://security.shuffler.io/shuffle-mcp-demo) |
|
|
93
|
+
| [`<AppSearchDrawer />`](#2-appsearchdrawer--full-search-drawer) | Right-side drawer wrapping `<ShuffleMCP />` plus a "Your apps" status row. This is the exact "Add Ingestion Source" drawer used in production | [/shuffle-mcp-demo §2](https://security.shuffler.io/shuffle-mcp-demo) |
|
|
94
|
+
| [`<AppDetailDrawer />`](#3-appdetaildrawer--single-app-config) | Right-side drawer for a single app: list/edit/test authentications and try the MCP `tools/call` endpoint inline | [/shuffle-mcp-demo §3](https://security.shuffler.io/shuffle-mcp-demo) |
|
|
95
|
+
|
|
96
|
+
### 1. `<ShuffleMCP />` — inline search
|
|
97
|
+
|
|
98
|
+
Drop-in search component. By default, clicking a result opens `<AppDetailDrawer />` automatically.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { ShuffleMCP } from 'shuffle-mcps';
|
|
102
|
+
|
|
103
|
+
<ShuffleMCP
|
|
104
|
+
apiKey={user.apiKey}
|
|
105
|
+
inline
|
|
106
|
+
layout="grid"
|
|
107
|
+
gridColumns={3}
|
|
108
|
+
/>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Take over selection yourself (open your own modal, route to a detail page, kick off a custom auth flow):
|
|
92
112
|
|
|
93
113
|
```tsx
|
|
94
114
|
<ShuffleMCP
|
|
95
|
-
|
|
115
|
+
apiKey={user.apiKey}
|
|
96
116
|
inline
|
|
97
117
|
preventDefault
|
|
98
|
-
onAppSelected={(
|
|
118
|
+
onAppSelected={({ app, authUrl }) => {
|
|
119
|
+
navigate(`/integrations/${app.objectID}`);
|
|
120
|
+
}}
|
|
99
121
|
/>
|
|
100
122
|
```
|
|
101
123
|
|
|
102
|
-
|
|
124
|
+
### 2. `<AppSearchDrawer />` — full search drawer
|
|
125
|
+
|
|
126
|
+
The complete "Add Ingestion Source" experience: header, your authenticated apps strip, search, and the Shuffle Pipelines banner. Clicking an app opens `<AppDetailDrawer />` for auth + MCP testing.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useState } from 'react';
|
|
130
|
+
import { AppSearchDrawer } from 'shuffle-mcps';
|
|
131
|
+
|
|
132
|
+
const [open, setOpen] = useState(false);
|
|
133
|
+
|
|
134
|
+
<>
|
|
135
|
+
<button onClick={() => setOpen(true)}>Add integration</button>
|
|
136
|
+
|
|
137
|
+
<AppSearchDrawer
|
|
138
|
+
open={open}
|
|
139
|
+
onClose={() => setOpen(false)}
|
|
140
|
+
title="Add Ingestion Source"
|
|
141
|
+
subtitle="Search and authenticate a tool to ingest incidents from"
|
|
142
|
+
/>
|
|
143
|
+
</>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Useful props:
|
|
147
|
+
|
|
148
|
+
| Prop | Type | Description |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `open` | `boolean` | Controlled open state |
|
|
151
|
+
| `onClose` | `() => void` | Called on close |
|
|
152
|
+
| `title` / `subtitle` | `string` | Header copy |
|
|
153
|
+
| `anchor` | `'left' \| 'right'` | Default `'right'` |
|
|
154
|
+
| `width` | `number` | Drawer width in px. Default `560` |
|
|
155
|
+
| `pinnedApps` | `Array<{ name, image_url, ... }>` | Apps pinned to the top of search results |
|
|
156
|
+
| `onQuickSelect` | `(app) => void` | Skip the detail drawer and call this directly |
|
|
157
|
+
| `onSelectOverride` | `(app) => boolean` | Intercept selection — return `true` to prevent default |
|
|
158
|
+
| `priorityCategory` | `string` | Sort matching apps to the top of the "Your apps" strip |
|
|
159
|
+
| `showPipelinesBanner` | `boolean` | Show the Shuffle Pipelines CTA |
|
|
160
|
+
|
|
161
|
+
### 3. `<AppDetailDrawer />` — single-app config
|
|
162
|
+
|
|
163
|
+
The right-side drawer that shows everything about one app: existing auth entries (with Configured / Tested / Inactive chips), an inline form to add or edit credentials, a **Test connection** button, an **Activate / Deactivate** toggle, and a built-in MCP `tools/call` chat to try it without leaving the page.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { useState } from 'react';
|
|
167
|
+
import { AppDetailDrawer } from 'shuffle-mcps';
|
|
168
|
+
|
|
169
|
+
const [appName, setAppName] = useState<string | null>(null);
|
|
170
|
+
|
|
171
|
+
<>
|
|
172
|
+
<button onClick={() => setAppName('Gmail')}>Configure Gmail</button>
|
|
173
|
+
|
|
174
|
+
<AppDetailDrawer
|
|
175
|
+
open={appName !== null}
|
|
176
|
+
onClose={() => setAppName(null)}
|
|
177
|
+
appName={appName}
|
|
178
|
+
/>
|
|
179
|
+
</>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Useful props:
|
|
183
|
+
|
|
184
|
+
| Prop | Type | Description |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `open` | `boolean` | Controlled open state |
|
|
187
|
+
| `onClose` | `() => void` | Called on close |
|
|
188
|
+
| `appName` | `string \| null` | The app to load. Resolved against Algolia by name |
|
|
189
|
+
| `anchor` | `'left' \| 'right'` | Default `'right'` |
|
|
190
|
+
| `width` | `number` | Drawer width in px. Default `520` |
|
|
191
|
+
| `onRefresh` | `() => void` | Called after a successful save/test, so the parent can re-fetch |
|
|
192
|
+
| `onAddToCanvas` | `(info) => void` | Replaces the **Activate** CTA with **+ Add** — used by the workflow editor |
|
|
193
|
+
| `isAuthenticated` | `boolean` | Whether the current user is signed in. Default `true` |
|
|
194
|
+
|
|
195
|
+
Reference implementation in production: [`src/Shuffle-MCPs/AppSearchDrawer.tsx`](./AppSearchDrawer.tsx) and [`AppDetailDrawer.tsx`](./AppDetailDrawer.tsx).
|
|
103
196
|
|
|
104
197
|
### Predefined search
|
|
105
198
|
|
|
106
199
|
```tsx
|
|
107
200
|
<ShuffleMCP
|
|
108
|
-
|
|
201
|
+
apiKey="..."
|
|
109
202
|
inline
|
|
110
203
|
initialFilterQuery="siem"
|
|
111
204
|
placeholder="Search SIEM tools..."
|
|
@@ -118,7 +211,7 @@ Full two-drawer reference implementation: [`src/components/shared/AppSearchDrawe
|
|
|
118
211
|
const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
|
|
119
212
|
|
|
120
213
|
<ShuffleMCP
|
|
121
|
-
|
|
214
|
+
apiKey="..."
|
|
122
215
|
inline
|
|
123
216
|
multiSelect
|
|
124
217
|
showCheckbox
|
|
@@ -131,7 +224,6 @@ const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
|
|
|
131
224
|
|
|
132
225
|
```tsx
|
|
133
226
|
<ShuffleMCP
|
|
134
|
-
authToken={user.token}
|
|
135
227
|
apiKey={user.apiKey}
|
|
136
228
|
apiBaseUrl="https://your-backend.example.com"
|
|
137
229
|
algoliaAppId="YOUR_APP_ID"
|
|
@@ -139,30 +231,91 @@ const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
|
|
|
139
231
|
/>
|
|
140
232
|
```
|
|
141
233
|
|
|
234
|
+
> **About `apiKey`:** this is the **current user's personal Shuffle API key**, not an org-level or service token. Your backend should look up the signed-in user's API key (wherever you store it — DB, session, etc.) and pass it through to this component. The component sends it as `Authorization: Bearer` on every API request and forwards it into the auth handoff URL as `&auth=`, so all reads and authentications happen as that user.
|
|
235
|
+
|
|
142
236
|
When `apiKey` is set, the component also fetches the user's **private apps** from `/api/v1/apps` and merges them into the search results, with an **All / Public / Private** filter shown above the list. Status dots appear too: validated, configured, selected, inactive.
|
|
143
237
|
|
|
144
238
|
## Common props
|
|
145
239
|
|
|
146
240
|
| Prop | Type | Description |
|
|
147
241
|
|---|---|---|
|
|
148
|
-
| `
|
|
242
|
+
| `apiKey` | `string` | The **current user's** Shuffle API key. Your backend should fetch it from wherever you store the user's credentials and pass it in. Used as `Authorization: Bearer` on every request and forwarded into the auth handoff URL as `&auth=`. This is the canonical credential. |
|
|
243
|
+
| `~~authToken~~` | `string` | **Deprecated.** Use `apiKey`. Kept for back-compat — only used as the auth-URL token if `apiKey` is not set. |
|
|
244
|
+
| `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. |
|
|
149
245
|
| `inline` | `boolean` | Inline results vs floating dropdown. |
|
|
150
246
|
| `layout` | `'list' \| 'grid'` | Result layout. Default `'list'`. |
|
|
151
247
|
| `initialFilterQuery` | `string` | Pre-filter without filling the input. |
|
|
152
|
-
| `preventDefault` | `boolean` | Skip default `window.open(authUrl)` so you can handle selection. |
|
|
153
|
-
| `onAppSelected` | `(detail) => void` | Fires on single-select pick. |
|
|
248
|
+
| `preventDefault` | `boolean` | Skip the default click behavior (built-in drawer or `window.open(authUrl)`) so you can handle selection yourself. |
|
|
249
|
+
| `onAppSelected` | `(detail) => void` | Fires on single-select pick. If provided, the built-in drawer is **not** opened — you take over. |
|
|
154
250
|
| `multiSelect` | `boolean` | Allow selecting multiple apps. |
|
|
155
|
-
| `apiKey` | `string` | Bearer token. Enables status dots and merges your private apps from `/api/v1/apps`. |
|
|
156
251
|
| `showSourceFilter` | `boolean` | Toggle the All / Public / Private filter. Default `true`. |
|
|
157
252
|
| `customStyles` | `CustomStyles` | Per-slot style overrides. |
|
|
158
253
|
|
|
254
|
+
### Default click behavior
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
### Org scoping
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
<ShuffleMCP
|
|
262
|
+
apiKey={user.apiKey} // current user's Shuffle API key, fetched by your backend
|
|
263
|
+
orgId={currentOrg.id} // every request now scoped to this org
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
## Theming (light + dark)
|
|
269
|
+
|
|
270
|
+
There is no `theme` prop — every component reads from your app's CSS variables and inherits whatever palette is active. Define these tokens once in your global stylesheet (e.g. `:root` for light, `.dark` for dark) and the components flip automatically:
|
|
271
|
+
|
|
272
|
+
```css
|
|
273
|
+
:root {
|
|
274
|
+
--background: 0 0% 100%;
|
|
275
|
+
--foreground: 222 47% 11%;
|
|
276
|
+
--card: 0 0% 100%;
|
|
277
|
+
--border: 220 13% 91%;
|
|
278
|
+
--muted: 220 14% 96%;
|
|
279
|
+
--muted-foreground: 220 9% 46%;
|
|
280
|
+
--primary: 24 100% 50%; /* Shuffle orange */
|
|
281
|
+
--primary-foreground: 0 0% 100%;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.dark {
|
|
285
|
+
--background: 222 47% 6%;
|
|
286
|
+
--foreground: 0 0% 98%;
|
|
287
|
+
--card: 222 47% 9%;
|
|
288
|
+
--border: 217 19% 18%;
|
|
289
|
+
--muted: 217 19% 14%;
|
|
290
|
+
--muted-foreground: 220 9% 65%;
|
|
291
|
+
--primary: 24 100% 55%;
|
|
292
|
+
--primary-foreground: 0 0% 100%;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Values are raw HSL channels (no `hsl(...)` wrapper) because internally we use `hsl(var(--card))`, `hsl(var(--border) / 0.5)`, etc. Toggle dark mode by adding/removing the `.dark` class on `<html>` or `<body>` — same pattern as Tailwind / shadcn.
|
|
297
|
+
|
|
298
|
+
For finer-grained overrides on `<ShuffleMCP />`, pass `customStyles` — every slot (container, input, dropdown, gridItem, appName, appDescription, appCategory, appTags, emptyState, errorState, …) accepts a `React.CSSProperties` object. See [LIBRARY.md](./LIBRARY.md#styling) for the full slot list.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
<ShuffleMCP
|
|
302
|
+
apiKey="..."
|
|
303
|
+
inline
|
|
304
|
+
customStyles={{
|
|
305
|
+
container: { borderRadius: 16 },
|
|
306
|
+
input: { fontFamily: 'Inter, sans-serif' },
|
|
307
|
+
gridItem: { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))' },
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
```
|
|
311
|
+
|
|
159
312
|
Full prop reference, framework setup (Next.js, Vue), styling slots, custom rendering, and publishing: [**LIBRARY.md**](./LIBRARY.md).
|
|
160
313
|
|
|
161
314
|
## Imperative handle
|
|
162
315
|
|
|
163
316
|
```tsx
|
|
164
317
|
const ref = useRef<ShuffleMCPHandle>(null);
|
|
165
|
-
<ShuffleMCP ref={ref}
|
|
318
|
+
<ShuffleMCP ref={ref} apiKey="..." inline />
|
|
166
319
|
|
|
167
320
|
ref.current?.search('slack');
|
|
168
321
|
ref.current?.clear();
|