@shuffleio/shuffle-mcps 0.0.4 → 0.0.6

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
@@ -8,7 +8,6 @@ Turn 3,000+ SaaS tools into MCP servers your agents can use. One search, one cli
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/@shuffleio/shuffle-mcps.svg)](https://www.npmjs.com/package/@shuffleio/shuffle-mcps)
10
10
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://www.npmjs.com/package/@shuffleio/shuffle-mcps)
11
- [![upstream](https://img.shields.io/badge/upstream-Shuffle%2Fsingul.js-orange)](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
  &nbsp;
31
30
 
32
- > **See it live:** [security.shuffler.io/apps](https://security.shuffler.io/apps) uses this package in production to power its 3,000+ integration picker.
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
- authToken="any-token"
76
+ apiKey="YOUR_SHUFFLE_API_KEY"
78
77
  inline
79
78
  layout="grid"
80
79
  gridColumns={3}
@@ -84,28 +83,168 @@ export default function App() {
84
83
  }
85
84
  ```
86
85
 
87
- ## Recipes
86
+ ## The components
88
87
 
89
- ### Search → detail drawer
88
+ The library exports four React components — use one or all of them. They share state, so you can mix-and-match.
90
89
 
91
- The most common pattern. Set `preventDefault` and handle `onAppSelected` to chain into your own auth or detail UI:
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
+ | [`<AgentUI />`](#4-agentui--start--debug-agents) | Standalone "What do you want to do?" hero prompt + live decision-by-decision debugger for `/api/v1/agent` runs. Drop-in replacement for the legacy Shuffle Core agent page | [/agents](https://security.shuffler.io/agents) |
96
+
97
+ ### 1. `<ShuffleMCP />` — inline search
98
+
99
+ Drop-in search component. By default, clicking a result opens `<AppDetailDrawer />` automatically.
92
100
 
93
101
  ```tsx
102
+ import { ShuffleMCP } from 'shuffle-mcps';
103
+
94
104
  <ShuffleMCP
95
- authToken={token}
105
+ apiKey={user.apiKey}
106
+ inline
107
+ layout="grid"
108
+ gridColumns={3}
109
+ />
110
+ ```
111
+
112
+ Take over selection yourself (open your own modal, route to a detail page, kick off a custom auth flow):
113
+
114
+ ```tsx
115
+ <ShuffleMCP
116
+ apiKey={user.apiKey}
96
117
  inline
97
118
  preventDefault
98
- onAppSelected={(d) => openDetailDrawer(d.app)}
119
+ onAppSelected={({ app, authUrl }) => {
120
+ navigate(`/integrations/${app.objectID}`);
121
+ }}
122
+ />
123
+ ```
124
+
125
+ ### 2. `<AppSearchDrawer />` — full search drawer
126
+
127
+ 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.
128
+
129
+ ```tsx
130
+ import { useState } from 'react';
131
+ import { AppSearchDrawer } from 'shuffle-mcps';
132
+
133
+ const [open, setOpen] = useState(false);
134
+
135
+ <>
136
+ <button onClick={() => setOpen(true)}>Add integration</button>
137
+
138
+ <AppSearchDrawer
139
+ open={open}
140
+ onClose={() => setOpen(false)}
141
+ title="Add Ingestion Source"
142
+ subtitle="Search and authenticate a tool to ingest incidents from"
143
+ />
144
+ </>
145
+ ```
146
+
147
+ Useful props:
148
+
149
+ | Prop | Type | Description |
150
+ |---|---|---|
151
+ | `open` | `boolean` | Controlled open state |
152
+ | `onClose` | `() => void` | Called on close |
153
+ | `title` / `subtitle` | `string` | Header copy |
154
+ | `anchor` | `'left' \| 'right'` | Default `'right'` |
155
+ | `width` | `number` | Drawer width in px. Default `560` |
156
+ | `pinnedApps` | `Array<{ name, image_url, ... }>` | Apps pinned to the top of search results |
157
+ | `onQuickSelect` | `(app) => void` | Skip the detail drawer and call this directly |
158
+ | `onSelectOverride` | `(app) => boolean` | Intercept selection — return `true` to prevent default |
159
+ | `priorityCategory` | `string` | Sort matching apps to the top of the "Your apps" strip |
160
+ | `showPipelinesBanner` | `boolean` | Show the Shuffle Pipelines CTA |
161
+
162
+ ### 3. `<AppDetailDrawer />` — single-app config
163
+
164
+ 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.
165
+
166
+ ```tsx
167
+ import { useState } from 'react';
168
+ import { AppDetailDrawer } from 'shuffle-mcps';
169
+
170
+ const [appName, setAppName] = useState<string | null>(null);
171
+
172
+ <>
173
+ <button onClick={() => setAppName('Gmail')}>Configure Gmail</button>
174
+
175
+ <AppDetailDrawer
176
+ open={appName !== null}
177
+ onClose={() => setAppName(null)}
178
+ appName={appName}
179
+ />
180
+ </>
181
+ ```
182
+
183
+ Useful props:
184
+
185
+ | Prop | Type | Description |
186
+ |---|---|---|
187
+ | `open` | `boolean` | Controlled open state |
188
+ | `onClose` | `() => void` | Called on close |
189
+ | `appName` | `string \| null` | The app to load. Resolved against Algolia by name |
190
+ | `anchor` | `'left' \| 'right'` | Default `'right'` |
191
+ | `width` | `number` | Drawer width in px. Default `520` |
192
+ | `onRefresh` | `() => void` | Called after a successful save/test, so the parent can re-fetch |
193
+ | `onAddToCanvas` | `(info) => void` | Replaces the **Activate** CTA with **+ Add** — used by the workflow editor |
194
+ | `isAuthenticated` | `boolean` | Whether the current user is signed in. Default `true` |
195
+
196
+ Reference implementation in production: [`src/Shuffle-MCPs/AppSearchDrawer.tsx`](./AppSearchDrawer.tsx) and [`AppDetailDrawer.tsx`](./AppDetailDrawer.tsx).
197
+
198
+ ### 4. `<AgentUI />` — start + debug agents
199
+
200
+ A standalone "What do you want to do?" surface for the Shuffle agent. Two modes in one component:
201
+
202
+ 1. **Starter** — large hero prompt with attached MCP/app chips. Submits to `/api/v1/agent`.
203
+ 2. **Debugger** — compact header + live decision timeline driven by `/api/v1/streams/results`, with question/continuation forms, per-decision raw-JSON inspection, approvals, retries, and reruns.
204
+
205
+ It switches modes automatically when an execution starts, or when `?execution_id=...&authorization=...` is present in the URL.
206
+
207
+ ```tsx
208
+ import { AgentUI } from 'shuffle-mcps';
209
+
210
+ <AgentUI
211
+ apiKey={user.apiKey} // optional — falls back to session
212
+ apiBaseUrl="https://shuffler.io" // optional — falls back to default
213
+ orgId={user.orgId} // optional — sent as Org-Id header
214
+ defaultApps={[{ name: 'http' }, { name: 'shuffle_tools' }]}
215
+ onRun={({ input, success, executionId }) => {
216
+ console.log('agent run', { input, success, executionId });
217
+ }}
99
218
  />
100
219
  ```
101
220
 
102
- Full two-drawer reference implementation: [`src/components/shared/AppSearchDrawer.tsx`](../components/shared/AppSearchDrawer.tsx).
221
+ Like `<ShuffleMCP />`, all three of `apiKey`, `apiBaseUrl`, and `orgId` are **optional**. When omitted, the component uses the shared browser session (cookie + `localStorage.shuffle_api_key`) — the same behavior as the rest of this library.
222
+
223
+ Useful props:
224
+
225
+ | Prop | Type | Description |
226
+ |---|---|---|
227
+ | `apiKey` | `string` | Bearer token used for every `/api/v1/*` call this instance makes |
228
+ | `apiBaseUrl` | `string` | Shuffle backend base URL (e.g. `https://shuffler.io`) |
229
+ | `orgId` | `string` | Sent as the `Org-Id` header on every call |
230
+ | `apps` | `AgentUIApp[]` | Controlled chip set. Disables auto-load |
231
+ | `defaultApps` | `AgentUIApp[]` | Initial chip set when `apps` is not provided |
232
+ | `autoLoadApps` | `boolean` | Auto-fetch the user's authenticated apps via `/api/v1/apps/authentication`. Default `true` |
233
+ | `executionId` / `authorization` | `string` | Attach to a known execution on mount and skip the starter |
234
+ | `readUrlParams` | `boolean` | Read `?execution_id` + `?authorization` from the URL. Default `true` |
235
+ | `title` / `subtitle` / `placeholder` | `string` | Hero copy |
236
+ | `defaultInput` / `autoSubmit` | `string` / `boolean` | Pre-fill and optionally submit on mount |
237
+ | `compact` / `hideHeroIcon` / `hideAppPicker` / `hideAttach` | `boolean` | Layout switches for embedding |
238
+ | `maxWidth` | `number` | Max width of the centered card. Default `900` |
239
+ | `onRun` | `(info) => void` | Fires whenever a run finishes (success or failure) |
240
+
241
+ Reference page in production: [`src/pages/dashboard/AgentsPage.tsx`](../pages/dashboard/AgentsPage.tsx) (mounted at [`/agents`](https://security.shuffler.io/agents)).
103
242
 
104
243
  ### Predefined search
105
244
 
106
245
  ```tsx
107
246
  <ShuffleMCP
108
- authToken="..."
247
+ apiKey="..."
109
248
  inline
110
249
  initialFilterQuery="siem"
111
250
  placeholder="Search SIEM tools..."
@@ -118,7 +257,7 @@ Full two-drawer reference implementation: [`src/components/shared/AppSearchDrawe
118
257
  const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
119
258
 
120
259
  <ShuffleMCP
121
- authToken="..."
260
+ apiKey="..."
122
261
  inline
123
262
  multiSelect
124
263
  showCheckbox
@@ -131,7 +270,6 @@ const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
131
270
 
132
271
  ```tsx
133
272
  <ShuffleMCP
134
- authToken={user.token}
135
273
  apiKey={user.apiKey}
136
274
  apiBaseUrl="https://your-backend.example.com"
137
275
  algoliaAppId="YOUR_APP_ID"
@@ -139,13 +277,16 @@ const [picked, setPicked] = useState<AlgoliaSearchApp[]>([]);
139
277
  />
140
278
  ```
141
279
 
280
+ > **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.
281
+
142
282
  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
283
 
144
284
  ## Common props
145
285
 
146
286
  | Prop | Type | Description |
147
287
  |---|---|---|
148
- | `authToken` | `string` | Required. Forwarded into the auth URL. |
288
+ | `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. |
289
+ | `~~authToken~~` | `string` | **Deprecated.** Use `apiKey`. Kept for back-compat — only used as the auth-URL token if `apiKey` is not set. |
149
290
  | `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. |
150
291
  | `inline` | `boolean` | Inline results vs floating dropdown. |
151
292
  | `layout` | `'list' \| 'grid'` | Result layout. Default `'list'`. |
@@ -153,7 +294,6 @@ When `apiKey` is set, the component also fetches the user's **private apps** fro
153
294
  | `preventDefault` | `boolean` | Skip the default click behavior (built-in drawer or `window.open(authUrl)`) so you can handle selection yourself. |
154
295
  | `onAppSelected` | `(detail) => void` | Fires on single-select pick. If provided, the built-in drawer is **not** opened — you take over. |
155
296
  | `multiSelect` | `boolean` | Allow selecting multiple apps. |
156
- | `apiKey` | `string` | Bearer token. Enables status dots and merges your private apps from `/api/v1/apps`. |
157
297
  | `showSourceFilter` | `boolean` | Toggle the All / Public / Private filter. Default `true`. |
158
298
  | `customStyles` | `CustomStyles` | Per-slot style overrides. |
159
299
 
@@ -165,20 +305,63 @@ When you do **not** pass `onAppSelected`, clicking an app opens a built-in right
165
305
 
166
306
  ```tsx
167
307
  <ShuffleMCP
168
- authToken={token}
169
- apiKey={user.apiKey}
170
- orgId={currentOrg.id} // every request now scoped to this org
308
+ apiKey={user.apiKey} // current user's Shuffle API key, fetched by your backend
309
+ orgId={currentOrg.id} // every request now scoped to this org
171
310
  />
172
311
  ```
173
312
 
174
313
 
314
+ ## Theming (light + dark)
315
+
316
+ 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:
317
+
318
+ ```css
319
+ :root {
320
+ --background: 0 0% 100%;
321
+ --foreground: 222 47% 11%;
322
+ --card: 0 0% 100%;
323
+ --border: 220 13% 91%;
324
+ --muted: 220 14% 96%;
325
+ --muted-foreground: 220 9% 46%;
326
+ --primary: 24 100% 50%; /* Shuffle orange */
327
+ --primary-foreground: 0 0% 100%;
328
+ }
329
+
330
+ .dark {
331
+ --background: 222 47% 6%;
332
+ --foreground: 0 0% 98%;
333
+ --card: 222 47% 9%;
334
+ --border: 217 19% 18%;
335
+ --muted: 217 19% 14%;
336
+ --muted-foreground: 220 9% 65%;
337
+ --primary: 24 100% 55%;
338
+ --primary-foreground: 0 0% 100%;
339
+ }
340
+ ```
341
+
342
+ 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.
343
+
344
+ 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.
345
+
346
+ ```tsx
347
+ <ShuffleMCP
348
+ apiKey="..."
349
+ inline
350
+ customStyles={{
351
+ container: { borderRadius: 16 },
352
+ input: { fontFamily: 'Inter, sans-serif' },
353
+ gridItem: { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))' },
354
+ }}
355
+ />
356
+ ```
357
+
175
358
  Full prop reference, framework setup (Next.js, Vue), styling slots, custom rendering, and publishing: [**LIBRARY.md**](./LIBRARY.md).
176
359
 
177
360
  ## Imperative handle
178
361
 
179
362
  ```tsx
180
363
  const ref = useRef<ShuffleMCPHandle>(null);
181
- <ShuffleMCP ref={ref} authToken="..." inline />
364
+ <ShuffleMCP ref={ref} apiKey="..." inline />
182
365
 
183
366
  ref.current?.search('slack');
184
367
  ref.current?.clear();