@shuffleio/shuffle-mcps 0.0.12 → 0.0.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuffleio/shuffle-mcps",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Turn 3,000+ SaaS tools into MCP servers your agents can use. Headless React component for search, selection, and OAuth handoff.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,15 +18,14 @@
18
18
  "shuffle",
19
19
  "singul"
20
20
  ],
21
- "type": "module",
22
- "main": "./dist/index.cjs",
23
- "module": "./dist/index.js",
21
+ "main": "./dist/index.js",
22
+ "module": "./dist/index.mjs",
24
23
  "types": "./dist/index.d.ts",
25
24
  "exports": {
26
25
  ".": {
27
26
  "types": "./dist/index.d.ts",
28
- "import": "./dist/index.js",
29
- "require": "./dist/index.cjs"
27
+ "import": "./dist/index.mjs",
28
+ "require": "./dist/index.js"
30
29
  },
31
30
  "./shuffle-mcp.css": "./dist/shuffle-mcp.css"
32
31
  },
@@ -49,20 +48,17 @@
49
48
  "@mui/material": ">=5",
50
49
  "@emotion/react": ">=11",
51
50
  "@emotion/styled": ">=11",
52
- "framer-motion": ">=10",
53
- "sonner": ">=1",
54
51
  "lucide-react": ">=0.300.0",
55
- "react-router-dom": ">=6",
56
- "react-markdown": ">=8",
57
- "remark-gfm": ">=3"
58
- },
59
- "peerDependenciesMeta": {
60
- "react-router-dom": {
61
- "optional": true
62
- }
52
+ "react-router-dom": ">=6"
63
53
  },
64
54
  "dependencies": {
65
- "algoliasearch": "^5.0.0"
55
+ "algoliasearch": "^5.0.0",
56
+ "framer-motion": "^11.0.0",
57
+ "sonner": "^1.5.0",
58
+ "react-markdown": "^10.1.0",
59
+ "remark-gfm": "^4.0.0",
60
+ "remark-breaks": "^4.0.0",
61
+ "react18-json-view": "^0.2.9"
66
62
  },
67
63
  "devDependencies": {
68
64
  "@types/react": "^18.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../api.ts","../appsCache.ts"],"sourcesContent":["/**\n * API Configuration\n * \n * Configure the base URL based on your deployment:\n * - Shuffle Cloud (EU): https://shuffler.io\n * - Shuffle Cloud (US): https://us.shuffler.io \n * - Self-hosted: Your own backend URL (e.g., https://shuffle.yourdomain.com)\n *\n * The region URL is dynamically resolved from /api/v1/getinfo's `region_url` field.\n * If the user switches orgs, it resets to the default until getinfo is called again.\n */\n\nconst DEV_BACKEND = 'https://tunnel.schemaless.org';\nconst PROD_BACKEND = 'https://shuffler.io';\n\n// Base URL for Shuffle Automation dashboard (used in tool switcher)\nexport const SHUFFLE_AUTOMATION_URL = 'https://shuffler.io/new-dashboard';\n\n// Known cloud domains that should always use shuffler.io as the default backend\nconst CLOUD_DOMAINS = ['security.shuffler.io', 'shutdown.no'];\n\n// Safely read Vite-style env vars without depending on `vite/client` types\n// (the published library should not require Vite to be installed).\nconst getEnvVar = (key: string): string | undefined => {\n try {\n const meta = import.meta as unknown as { env?: Record<string, string | undefined> };\n return meta?.env?.[key];\n } catch {\n return undefined;\n }\n};\n\n// Determine if we're in Lovable preview (dev) or published (prod)\nexport const isDevEnvironment = (): boolean => {\n if (getEnvVar('VITE_SHUFFLE_API_URL')) return false;\n const hostname = typeof window !== 'undefined' ? window.location.hostname : '';\n return hostname.includes('lovableproject.com') || hostname.includes('id-preview--');\n};\n\n/** Check if running on a known Shuffle Cloud domain */\nexport const isCloudDomain = (): boolean => {\n const hostname = typeof window !== 'undefined' ? window.location.hostname : '';\n return CLOUD_DOMAINS.includes(hostname);\n};\n\n/**\n * True when running on Shuffle Cloud (a known *.shuffler.io / shutdown.no domain).\n * Use this to gate cloud-only features like Google Analytics (ReactGA).\n *\n * isCloud() → cloud deployment, GA allowed, telemetry OK\n * !isCloud() → either Lovable preview (dev) OR self-hosted onprem; do NOT call GA\n */\nexport const isCloud = (): boolean => isCloudDomain();\n\n/**\n * True when running self-hosted (onprem) — i.e. NOT in Lovable preview AND NOT on a known cloud domain.\n */\nexport const isOnprem = (): boolean => !isDevEnvironment() && !isCloudDomain();\n\nconst getDefaultBaseUrl = (): string => {\n const envUrl = getEnvVar('VITE_SHUFFLE_API_URL');\n if (envUrl) {\n return envUrl;\n }\n if (isDevEnvironment()) return DEV_BACKEND;\n // Cloud domains always default to shuffler.io; region_url from getinfo may override later\n if (isCloudDomain()) return PROD_BACKEND;\n // Self-hosted / on-prem: use current domain (nginx proxies /api/* to backend)\n if (typeof window !== 'undefined') {\n return window.location.origin;\n }\n return PROD_BACKEND;\n};\n\n// Dynamic region URL state (only applies in production, not dev)\nlet _regionUrl: string | null = null;\nlet _trackedOrgId: string | null = null;\n\n/** Check if a URL is a valid shuffler.io subdomain */\nconst isShufflerSubdomain = (url: string): boolean => {\n try {\n const parsed = new URL(url);\n return parsed.hostname.endsWith('.shuffler.io') || parsed.hostname === 'shuffler.io';\n } catch {\n return false;\n }\n};\n\n/**\n * Set the dynamic region URL from getinfo response.\n * Only applies if the URL is a shuffler.io subdomain and different from default.\n */\nexport const setRegionUrl = (regionUrl: string | undefined | null, orgId: string | undefined | null) => {\n // In dev environments, don't override — always use dev backend\n if (isDevEnvironment()) {\n _trackedOrgId = orgId || null;\n return;\n }\n\n _trackedOrgId = orgId || null;\n\n if (regionUrl && isShufflerSubdomain(regionUrl)) {\n // Normalize: strip trailing slash\n const normalized = regionUrl.replace(/\\/+$/, '');\n if (normalized !== PROD_BACKEND) {\n _regionUrl = normalized;\n console.log(`[API] Region URL set to: ${_regionUrl}`);\n return;\n }\n }\n\n // No valid region override — use default\n _regionUrl = null;\n};\n\n/**\n * Called when org changes. Resets region URL to default until next getinfo.\n */\nexport const resetRegionUrl = () => {\n if (_regionUrl) {\n console.log(`[API] Region URL reset to default (org changed)`);\n }\n _regionUrl = null;\n};\n\n/** Get the currently tracked org ID */\nexport const getTrackedOrgId = (): string | null => _trackedOrgId;\n\nexport const API_CONFIG = {\n // Shuffle backend URL - uses region URL if set, otherwise default\n get baseUrl(): string {\n return _regionUrl || getDefaultBaseUrl();\n },\n \n // API version\n version: 'v1',\n \n // Get API key from localStorage (for local development)\n get apiKey(): string | null {\n return localStorage.getItem('shuffle_api_key');\n },\n \n // Set API key in localStorage\n setApiKey(key: string | null) {\n if (key) {\n localStorage.setItem('shuffle_api_key', key);\n } else {\n localStorage.removeItem('shuffle_api_key');\n }\n },\n};\n\n// Computed API endpoint - pass full path including /api/v1 or /api/v2\nexport const getApiUrl = (endpoint: string): string => {\n return `${API_CONFIG.baseUrl}${endpoint}`;\n};\n\n/**\n * Resolve an agent approval / form URL (typically `/forms/{id}`) to the\n * original Shuffle Core where the form actually lives. On Cloud this is\n * always https://shuffler.io (regardless of the active region URL — forms\n * are served from core), and onprem / dev fall back to the configured\n * backend baseUrl. Absolute URLs are returned unchanged.\n */\nexport const getShuffleCoreFormUrl = (refUrl: string): string => {\n if (!refUrl) return refUrl;\n // Already absolute — trust it.\n if (/^https?:\\/\\//i.test(refUrl)) return refUrl;\n const path = refUrl.startsWith('/') ? refUrl : `/${refUrl}`;\n // Cloud always points at shuffler.io for forms.\n if (isCloud()) return `https://shuffler.io${path}`;\n // Self-hosted / dev — use the active backend.\n return `${API_CONFIG.baseUrl}${path}`;\n};\n\n/** True when a notification.reference_url points at an agent approval form. */\nexport const isAgentApprovalFormUrl = (refUrl: string | undefined | null): boolean => {\n if (!refUrl) return false;\n try {\n // Strip an optional origin so we can match the path consistently.\n const path = /^https?:\\/\\//i.test(refUrl) ? new URL(refUrl).pathname : refUrl;\n return /^\\/forms\\/[^/?#]+/.test(path);\n } catch {\n return false;\n }\n};\n\n/**\n * Central fetch wrapper that ALWAYS includes credentials and auth headers.\n * Use this instead of raw fetch() for all Shuffle API calls.\n */\nexport const shuffleFetch = (url: string, init?: RequestInit): Promise<Response> => {\n const { headers: extraHeaders, ...rest } = init || {};\n return fetch(url, {\n credentials: 'include',\n ...rest,\n headers: {\n ...getAuthHeader(),\n ...extraHeaders,\n },\n });\n};\n\n// Common endpoints\nexport const API_ENDPOINTS = {\n login: '/login',\n logout: '/logout',\n me: '/me',\n getinfo: '/getinfo',\n alerts: '/alerts',\n cases: '/cases',\n workflows: '/workflows',\n apps: '/apps',\n};\n\n// Get authorization header - uses API key if available, otherwise session token.\n// Always scopes the request to the currently-active org via Org-Id when known,\n// so a user in a sub-org reads/writes against that sub-org rather than their\n// default home org. Pass `overrideOrgId` to force a different org (e.g. when\n// reading data from another tenant in a multi-tenant view).\nexport const getAuthHeader = (overrideOrgId?: string | null): Record<string, string> => {\n const headers: Record<string, string> = {};\n\n // Only send Authorization header for API key auth.\n // Session-based (cookie) auth is handled by credentials: 'include' — never both.\n const apiKey = API_CONFIG.apiKey;\n if (apiKey) {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n // Scope to the active org. Explicit override beats the tracked org.\n const orgId = overrideOrgId ?? _trackedOrgId;\n if (orgId) {\n headers['Org-Id'] = orgId;\n }\n\n return headers;\n};\n\n","/**\n * Shared, request-coalescing fetcher for `/api/v1/apps`.\n *\n * The demo page (and the real app) renders many components that each need\n * the user's apps list — `ShuffleMCP`, `AgentUI`, `AppDetailDrawer`,\n * `useAppLookup`, `IntegrationStatus`. Without coordination they fire 5+\n * identical `/api/v1/apps` requests on mount.\n *\n * This module mirrors `authenticatedApps.ts`:\n * 1. Coalesces concurrent in-flight calls into a single Promise.\n * 2. Caches the resolved JSON for a short TTL.\n * 3. Exposes `invalidateAppsCache()` for callers that mutate apps.\n *\n * The cache key includes baseUrl + path + apiKey + orgId so multi-tenant /\n * multi-region setups stay isolated.\n */\n\nimport { API_CONFIG, getAuthHeader } from './api';\n\nexport interface FetchAppsOptions {\n baseUrl: string;\n path?: string;\n apiKey?: string | null;\n orgId?: string | null;\n}\n\ninterface CacheEntry {\n promise?: Promise<any[]>;\n data?: any[];\n fetchedAt?: number;\n}\n\nconst TTL_MS = 30_000;\nconst cache = new Map<string, CacheEntry>();\n\nconst cacheKey = (o: FetchAppsOptions): string =>\n `${o.baseUrl}|${o.path || '/api/v1/apps'}|${o.apiKey || ''}|${o.orgId || ''}`;\n\nconst doFetch = async (o: FetchAppsOptions): Promise<any[]> => {\n const headers: Record<string, string> = {};\n if (o.apiKey) headers['Authorization'] = `Bearer ${o.apiKey}`;\n if (o.orgId) headers['Org-Id'] = o.orgId;\n\n const response = await fetch(`${o.baseUrl}${o.path || '/api/v1/apps'}`, {\n credentials: 'include',\n headers,\n });\n if (!response.ok) return [];\n const data = await response.json();\n if (Array.isArray(data)) return data;\n return Array.isArray(data?.data) ? data.data : [];\n};\n\n/**\n * Get the apps list. Returns the cached value if it is still within the\n * TTL; otherwise fires (or joins) a single network request.\n */\nexport const fetchApps = (opts: FetchAppsOptions): Promise<any[]> => {\n const key = cacheKey(opts);\n const entry = cache.get(key) || {};\n\n if (entry.data && entry.fetchedAt && Date.now() - entry.fetchedAt < TTL_MS) {\n return Promise.resolve(entry.data);\n }\n if (entry.promise) return entry.promise;\n\n const promise = doFetch(opts)\n .then((data) => {\n cache.set(key, { data, fetchedAt: Date.now() });\n return data;\n })\n .catch((err) => {\n cache.delete(key);\n throw err;\n });\n\n cache.set(key, { ...entry, promise });\n return promise;\n};\n\n/** Invalidate the cached entry for one (or all) keys. */\nexport const invalidateAppsCache = (opts?: FetchAppsOptions) => {\n if (!opts) {\n cache.clear();\n return;\n }\n cache.delete(cacheKey(opts));\n};\n\n/**\n * Convenience wrapper for callers that already use API_CONFIG / getAuthHeader.\n * Coalesces against the same cache as `fetchApps`.\n */\n\nexport const fetchAppsViaApiConfig = (): Promise<any[]> => {\n const headers = getAuthHeader();\n const orgId = headers['Org-Id'] || null;\n return fetchApps({\n baseUrl: API_CONFIG.baseUrl,\n apiKey: API_CONFIG.apiKey,\n orgId,\n });\n};\n"],"mappings":"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","names":[]}