├── managed-auth-basic-next-app ├── .eslintignore ├── .tool-versions ├── .gitignore ├── next.config.mjs ├── postcss.config.js ├── app │ ├── favicon.ico │ ├── types.ts │ ├── _components │ │ ├── ExternalLink.tsx │ │ ├── ConnectDocs.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── CodePanel.tsx │ │ └── ConnectOptions.tsx │ ├── layout.tsx │ ├── globals.css │ ├── api │ │ └── pipedream │ │ │ └── token │ │ │ └── route.ts │ ├── page.tsx │ ├── server.ts │ └── _hooks │ │ ├── useAppsSearch.ts │ │ └── useConnectToken.ts ├── next-env.d.ts ├── .eslintrc.js ├── .env.example ├── public │ ├── typescript-icon.svg │ ├── vercel.svg │ └── next.svg ├── tailwind.config.ts ├── tsconfig.json ├── package.json ├── README.md └── .vscode │ └── launch.json ├── connect-react-demo ├── .tool-versions ├── .gitignore ├── app │ ├── favicon.ico │ ├── opengraph-image.png │ ├── components │ │ ├── customization-select │ │ │ ├── CustomIndicators.tsx │ │ │ ├── default-unstyled.ts │ │ │ ├── CustomLabel.tsx │ │ │ ├── dark-theme.ts │ │ │ └── blue-theme.ts │ │ ├── Cursor.tsx │ │ ├── config │ │ │ ├── ConfigSection.tsx │ │ │ ├── TabsHeader.tsx │ │ │ ├── AuthSection.tsx │ │ │ ├── CustomizationSection.tsx │ │ │ ├── FormSettingsSection.tsx │ │ │ └── CodeSection.tsx │ │ ├── Terminal.tsx │ │ ├── GoogleAnalytics.tsx │ │ ├── SectionHeader.tsx │ │ ├── DemoTabTrigger.tsx │ │ ├── ui │ │ │ ├── boolean-toggle.tsx │ │ │ ├── error-boundary.tsx │ │ │ └── code-block.tsx │ │ ├── ConfigAndCodePanel.tsx │ │ ├── DatadogScript.tsx │ │ ├── PageSkeleton.tsx │ │ ├── ComponentTypeSelector.tsx │ │ ├── ClientWrapper.tsx │ │ ├── TerminalCollapsible.tsx │ │ ├── DemoHeader.tsx │ │ ├── LiveCodePanel.tsx │ │ └── PipedreamLogo.tsx │ ├── page.tsx │ ├── actions │ │ └── backendClient.ts │ ├── layout.tsx │ └── globals.css ├── postcss.config.mjs ├── Makefile ├── lib │ ├── utils.ts │ ├── stable-uuid.tsx │ ├── query-params.tsx │ ├── constants │ │ ├── ui.ts │ │ └── design-tokens.ts │ ├── types │ │ └── pipedream.ts │ ├── backend-client.ts │ ├── hooks │ │ └── use-copy-to-clipboard.ts │ ├── use-query-params.tsx │ ├── utils │ │ └── type-descriptions.ts │ ├── format-utils.ts │ ├── env.ts │ └── code-templates.ts ├── next-env.d.ts ├── components │ └── ui │ │ ├── collapsible.tsx │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── switch.tsx │ │ ├── badge.tsx │ │ ├── tooltip.tsx │ │ ├── popover.tsx │ │ ├── scroll-area.tsx │ │ ├── resizable.tsx │ │ ├── tabs.tsx │ │ ├── button.tsx │ │ ├── dialog.tsx │ │ ├── command.tsx │ │ └── navigation-menu.tsx ├── components.json ├── .env.example ├── tsconfig.json ├── next.config.mjs ├── public │ └── pd_theme_basic.css ├── package.json ├── tailwind.config.ts ├── README.md └── scripts │ └── validate-env.js ├── mcp ├── pnpm-workspace.yaml ├── shared │ ├── pd.d.ts.map │ ├── config.d.ts.map │ ├── tsconfig.json │ ├── config.d.ts │ ├── package.json │ ├── pd.d.ts │ ├── pd.js.map │ ├── pd.js │ ├── pd.ts │ ├── cli.d.ts.map │ ├── config.js.map │ ├── config.ts │ ├── config.js │ ├── cli.d.ts │ ├── cli.js.map │ ├── cli.js │ └── cli.ts ├── .env.example ├── ai-sdk │ ├── config.ts │ ├── package.json │ ├── tsconfig.json │ ├── README.md │ └── index.ts ├── package.json ├── openai-sdk │ ├── tsconfig.json │ ├── package.json │ └── README.md └── README.md ├── .gitignore └── README.md /managed-auth-basic-next-app/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.10.0 2 | -------------------------------------------------------------------------------- /connect-react-demo/.tool-versions: -------------------------------------------------------------------------------- 1 | pnpm 9.14.2 2 | nodejs 20.13.1 3 | -------------------------------------------------------------------------------- /mcp/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'ai-sdk' 3 | - 'openai-sdk' 4 | - 'shared' -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.gitignore: -------------------------------------------------------------------------------- 1 | .env.* 2 | !.env.example 3 | node_modules 4 | .next/ 5 | -------------------------------------------------------------------------------- /connect-react-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | .env* 4 | !.env.example 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /connect-react-demo/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PipedreamHQ/pipedream-connect-examples/HEAD/connect-react-demo/app/favicon.ico -------------------------------------------------------------------------------- /managed-auth-basic-next-app/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /connect-react-demo/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PipedreamHQ/pipedream-connect-examples/HEAD/connect-react-demo/app/opengraph-image.png -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PipedreamHQ/pipedream-connect-examples/HEAD/managed-auth-basic-next-app/app/favicon.ico -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/types.ts: -------------------------------------------------------------------------------- 1 | export interface ConnectContext { 2 | externalUserId: string; 3 | token: string; 4 | connectLinkUrl: string | null; 5 | expiresAt: string; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /connect-react-demo/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /connect-react-demo/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | pnpm install 3 | pnpm dev 4 | 5 | connect-react-dev: 6 | LOCAL_CONNECT_REACT=1 pnpm install 7 | pnpm dev 8 | 9 | 10 | clean: 11 | rm -rf node_modules 12 | -------------------------------------------------------------------------------- /connect-react-demo/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /mcp/shared/pd.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pd.d.ts","sourceRoot":"","sources":["pd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjD,eAAO,MAAM,MAAM;;;;;;;CAalB,CAAC;AAEF,eAAO,MAAM,EAAE,iBAAwB,CAAC;AAExC,eAAO,MAAM,SAAS,GAAU,OAAO,MAAM;;;;;EAS5C,CAAC"} -------------------------------------------------------------------------------- /connect-react-demo/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import "./.next/dev/types/routes.d.ts"; 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /mcp/shared/config.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;GAGG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAuBjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAC3D,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GACrB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAmBzB"} -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/CustomIndicators.tsx: -------------------------------------------------------------------------------- 1 | import { components as ReactSelectComponents } from "react-select" 2 | import type { DropdownIndicatorProps } from "react-select" 3 | 4 | export function CustomDropdownIndicator(props: DropdownIndicatorProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: [ 5 | 'next/core-web-vitals', 6 | 'plugin:@typescript-eslint/recommended', 7 | ], 8 | plugins: ['@typescript-eslint'], 9 | rules: { 10 | // Add your custom rules here 11 | }, 12 | ignorePatterns: ['node_modules/', '.eslintrc.js'], 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/Cursor.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion" 2 | 3 | export const Cursor = () => ( 4 | 14 | ) 15 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /mcp/.env.example: -------------------------------------------------------------------------------- 1 | # AI providers 2 | OPENAI_API_KEY= # sk-xxx... 3 | ANTHROPIC_API_KEY= # sk-xxx... 4 | GOOGLE_GENERATIVE_AI_API_KEY= # 5 | EXA_API_KEY= # To enable web searches 6 | 7 | # Pipedream credentials 8 | # Read more here: https://pipedream.com/docs/connect/mcp/developers/#prerequisites 9 | PIPEDREAM_CLIENT_ID= 10 | PIPEDREAM_CLIENT_SECRET= 11 | PIPEDREAM_PROJECT_ID= # proj_xxxxxxx 12 | PIPEDREAM_PROJECT_ENVIRONMENT= # development | production 13 | -------------------------------------------------------------------------------- /mcp/ai-sdk/config.ts: -------------------------------------------------------------------------------- 1 | import { loadAndValidateConfig } from "../shared/config.js"; 2 | import { z } from "zod"; 3 | 4 | export const config = loadAndValidateConfig( 5 | z.object({ 6 | OPENAI_API_KEY: z.string(), 7 | PIPEDREAM_CLIENT_ID: z.string(), 8 | PIPEDREAM_CLIENT_SECRET: z.string(), 9 | PIPEDREAM_PROJECT_ID: z.string(), 10 | PIPEDREAM_PROJECT_ENVIRONMENT: z 11 | .enum(["development", "production"]) 12 | .default("development"), 13 | }) 14 | ); 15 | -------------------------------------------------------------------------------- /mcp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pipedream-mcp-examples", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Pipedream MCP CLI Examples", 6 | "scripts": { 7 | "build": "pnpm -r build", 8 | "ai-sdk": "pnpm --filter ./ai-sdk run start", 9 | "openai-sdk": "pnpm --filter ./openai-sdk run start" 10 | }, 11 | "keywords": [ 12 | "mcp", 13 | "pipedream", 14 | "ai", 15 | "examples" 16 | ], 17 | "author": "Pipedream", 18 | "license": "MIT" 19 | } -------------------------------------------------------------------------------- /connect-react-demo/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pnpm-debug.log* 7 | 8 | # Build outputs 9 | dist/ 10 | build/ 11 | .next/ 12 | out/ 13 | 14 | # Environment files 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | # IDE files 22 | .vscode/ 23 | .idea/ 24 | *.swp 25 | *.swo 26 | 27 | # OS files 28 | .DS_Store 29 | Thumbs.db 30 | 31 | # Temporary files 32 | *.tmp 33 | *.temp 34 | 35 | # TypeScript 36 | *.tsbuildinfo -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.env.example: -------------------------------------------------------------------------------- 1 | PIPEDREAM_CLIENT_ID= 2 | PIPEDREAM_CLIENT_SECRET= 3 | NEXT_PUBLIC_PIPEDREAM_PROJECT_ID= # does not typically need to be accessible by the frontend, but the demo app uses it 4 | PIPEDREAM_PROJECT_ENVIRONMENT=development 5 | PIPEDREAM_ALLOWED_ORIGINS='["https://example.com", "http://localhost:3000", "http://localhost:3001", "http://localhost:3002"]' 6 | NEXT_PUBLIC_PIPEDREAM_EXTERNAL_USER_ID= # Optional: set to reuse the same external user ID across reloads 7 | -------------------------------------------------------------------------------- /connect-react-demo/lib/stable-uuid.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { v4 as uuid } from "uuid" 3 | 4 | 5 | export const useStableUuid = (): [string, () => void] => { 6 | const [id, setId] = useState("") 7 | 8 | const refresh = () => { 9 | // Check for override env var first 10 | const overrideId = process.env.NEXT_PUBLIC_EXTERNAL_USER_ID 11 | setId(overrideId || `demo-${uuid()}`) 12 | } 13 | 14 | useEffect(() => { 15 | refresh() 16 | }, []) 17 | 18 | return [id, refresh] 19 | } 20 | -------------------------------------------------------------------------------- /mcp/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "declaration": true, 12 | "outDir": "./dist", 13 | "rootDir": "./", 14 | "types": ["node"] 15 | }, 16 | "include": [ 17 | "**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "dist" 22 | ] 23 | } -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/ConfigSection.tsx: -------------------------------------------------------------------------------- 1 | interface ConfigSectionProps { 2 | icon: React.ReactNode 3 | title: string 4 | children: React.ReactNode 5 | } 6 | 7 | export const ConfigSection = ({ 8 | icon, 9 | title, 10 | children, 11 | }: ConfigSectionProps) => ( 12 |
13 |
14 |
{icon}
15 |
{title}
16 |
17 |
{children}
18 |
19 | ) 20 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/_components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { ReactNode } from "react"; 4 | 5 | interface ExternalLinkProps { 6 | href: string; 7 | children: ReactNode; 8 | className?: string; 9 | } 10 | 11 | export function ExternalLink({ href, children, className = "" }: ExternalLinkProps) { 12 | return ( 13 | 19 | {children} 20 | 21 | ); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/public/typescript-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp/shared/config.d.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | /** 3 | * Load environment configuration for examples 4 | * Tries to load .env from current working directory first, then from project root 5 | */ 6 | export declare function loadConfig(): void; 7 | /** 8 | * Load and validate environment configuration with Zod schema 9 | * @param schema - Zod schema to validate environment variables 10 | * @returns Parsed and validated environment configuration 11 | */ 12 | export declare function loadAndValidateConfig(schema: z.ZodObject): z.infer>; 13 | //# sourceMappingURL=config.d.ts.map -------------------------------------------------------------------------------- /managed-auth-basic-next-app/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /mcp/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pipedream/shared", 3 | "version": "1.0.0", 4 | "description": "Shared utilities for MCP examples", 5 | "type": "module", 6 | "private": true, 7 | "main": "config.js", 8 | "scripts": { 9 | "build": "tsc", 10 | "dev": "tsc --watch" 11 | }, 12 | "dependencies": { 13 | "@modelcontextprotocol/sdk": "^1.12.3", 14 | "@pipedream/sdk": "2.0.0-rc.8", 15 | "commander": "^12.0.0", 16 | "dotenv": "^16.3.1", 17 | "openai": "^5.23.1", 18 | "zod": "^3.23.0" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20.10.0", 22 | "typescript": "^5.3.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcp/shared/pd.d.ts: -------------------------------------------------------------------------------- 1 | import { PipedreamClient } from "@pipedream/sdk"; 2 | export declare const config: { 3 | OPENAI_API_KEY: string; 4 | PIPEDREAM_CLIENT_ID: string; 5 | PIPEDREAM_CLIENT_SECRET: string; 6 | PIPEDREAM_PROJECT_ID: string; 7 | PIPEDREAM_PROJECT_ENVIRONMENT: "development" | "production"; 8 | MCP_HOST: string; 9 | }; 10 | export declare const pd: PipedreamClient; 11 | export declare const pdHeaders: (exuid: string) => Promise<{ 12 | Authorization: string; 13 | "x-pd-project-id": string; 14 | "x-pd-environment": "development" | "production"; 15 | "x-pd-external-user-id": string; 16 | }>; 17 | //# sourceMappingURL=pd.d.ts.map -------------------------------------------------------------------------------- /connect-react-demo/lib/query-params.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { z } from "zod"; 3 | 4 | export const appSlug = z.string().optional(); 5 | export const componentType = z.string().optional(); 6 | export const componentKeySlug = z.string().optional(); 7 | export const propNames = z.string().optional(); 8 | export const hideOptionalProps = z.string().optional(); 9 | export const enableDebugging = z.string().optional(); 10 | export const tab = z.string().optional(); 11 | 12 | export const queryParamSchema = z.object({ 13 | app: appSlug, 14 | component: componentKeySlug, 15 | propNames, 16 | hideOptionalProps, 17 | enableDebugging, 18 | type: componentType, 19 | tab, 20 | }); 21 | -------------------------------------------------------------------------------- /connect-react-demo/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata, ResolvingMetadata } from "next" 2 | import { Suspense } from "react" 3 | import { ClientWrapper } from "./components/ClientWrapper"; 4 | 5 | export async function generateMetadata( 6 | { searchParams: _searchParams }: {searchParams: Promise}, 7 | parent: ResolvingMetadata 8 | ): Promise { 9 | 10 | const title = (await parent).title?.absolute || "" 11 | 12 | return { 13 | title, 14 | }; 15 | } 16 | 17 | export default async function Home({searchParams: _searchParams}: {searchParams: Promise}) { 18 | return ( 19 | 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /connect-react-demo/.env.example: -------------------------------------------------------------------------------- 1 | # Required: Server-side OAuth credentials (available in your project settings) 2 | PIPEDREAM_CLIENT_ID= 3 | PIPEDREAM_CLIENT_SECRET= 4 | PIPEDREAM_PROJECT_ID= # Starts with 'proj_', available in the Pipedream UI 5 | PIPEDREAM_PROJECT_ENVIRONMENT=development 6 | PIPEDREAM_ALLOWED_ORIGINS='["https://example.com", "http://localhost:3000", "http://localhost:3001"]' 7 | 8 | # Required: Client-side project environment (should match PIPEDREAM_PROJECT_ENVIRONMENT) 9 | NEXT_PUBLIC_PIPEDREAM_PROJECT_ENVIRONMENT=development 10 | 11 | # Optional: Set a static external user ID for easier local debugging 12 | NEXT_PUBLIC_EXTERNAL_USER_ID=test-user-abc-123 13 | -------------------------------------------------------------------------------- /mcp/openai-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "allowJs": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": false, 15 | "outDir": "./dist", 16 | "rootDir": "./", 17 | "declaration": true, 18 | "declarationMap": true, 19 | "sourceMap": true 20 | }, 21 | "include": [ 22 | "**/*.ts", 23 | "**/*.js" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "dist" 28 | ] 29 | } -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Pipedream Connect Demo", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export const viewport: Viewport = { 13 | width: "device-width", 14 | initialScale: 1, 15 | maximumScale: 1, 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /mcp/ai-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "AI SDK example using Model Context Protocol (MCP)", 4 | "type": "module", 5 | "scripts": { 6 | "start": "tsx index.ts" 7 | }, 8 | "dependencies": { 9 | "@ai-sdk/openai": "^2.0.0", 10 | "@modelcontextprotocol/sdk": "^1.12.3", 11 | "ai": "^5.0.0", 12 | "commander": "^12.0.0", 13 | "dotenv": "^16.4.5", 14 | "openai": "^5.23.1", 15 | "tsx": "^4.7.1", 16 | "zod": "^4.1.8" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.0.0", 20 | "typescript": "^5.0.0" 21 | }, 22 | "keywords": [ 23 | "ai", 24 | "mcp", 25 | "examples", 26 | "ai-sdk" 27 | ], 28 | "author": "Pipedream", 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /mcp/openai-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "OpenAI SDK example using Model Context Protocol (MCP)", 4 | "type": "module", 5 | "scripts": { 6 | "build": "tsc", 7 | "start": "tsx index.ts" 8 | }, 9 | "dependencies": { 10 | "@modelcontextprotocol/sdk": "^1.12.3", 11 | "ai": "^4.3.16", 12 | "commander": "^12.0.0", 13 | "dotenv": "^16.4.5", 14 | "openai": "^5.23.1", 15 | "tsx": "^4.7.1", 16 | "zod": "^3.23.0" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.0.0", 20 | "typescript": "^5.0.0" 21 | }, 22 | "keywords": [ 23 | "ai", 24 | "mcp", 25 | "examples", 26 | "openai-sdk" 27 | ], 28 | "author": "Pipedream", 29 | "license": "MIT" 30 | } -------------------------------------------------------------------------------- /connect-react-demo/lib/constants/ui.ts: -------------------------------------------------------------------------------- 1 | export const TOGGLE_STYLES = { 2 | active: "bg-zinc-900 text-white", 3 | inactive: "bg-zinc-50 text-zinc-600 hover:bg-zinc-100", 4 | separator: "w-px bg-zinc-200" 5 | } as const 6 | 7 | export const DROPDOWN_PORTAL_CONFIG = { 8 | menuPortalTarget: typeof document !== 'undefined' ? document.body : undefined, 9 | menuPlacement: 'auto' as const, 10 | menuShouldBlockScroll: false, 11 | styles: { 12 | menuPortal: (base: any) => ({ ...base, zIndex: 9999 }), 13 | }, 14 | components: { 15 | IndicatorSeparator: () => null, 16 | } 17 | } as const 18 | 19 | export const TOOLTIP_COLORS = { 20 | type: "#d73a49", 21 | property: "#6f42c1", 22 | value: "#22863a", 23 | number: "#005cc5" 24 | } as const -------------------------------------------------------------------------------- /mcp/ai-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "allowJs": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": false, 15 | "outDir": "./dist", 16 | "rootDir": "../", 17 | "declaration": true, 18 | "declarationMap": true, 19 | "sourceMap": true 20 | }, 21 | "include": [ 22 | "**/*.ts", 23 | "**/*.js", 24 | "../shared/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "dist" 29 | ] 30 | } -------------------------------------------------------------------------------- /connect-react-demo/lib/types/pipedream.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableProp } from "@pipedream/sdk" 2 | 3 | export interface Component { 4 | key: string 5 | configurable_props: ConfigurableProp[] 6 | // Add other component properties as needed 7 | } 8 | 9 | export interface AppResponse { 10 | name_slug: string 11 | // Add other app properties as needed 12 | } 13 | 14 | export interface CustomizationOption { 15 | name: string 16 | label: string 17 | customization?: Record 18 | file?: string 19 | containerStyle?: React.CSSProperties 20 | } 21 | 22 | export interface SDKError { 23 | name: string 24 | message: string 25 | code?: string 26 | details?: Record 27 | } 28 | 29 | export type ComponentType = "action" | "trigger" 30 | -------------------------------------------------------------------------------- /connect-react-demo/lib/backend-client.ts: -------------------------------------------------------------------------------- 1 | 2 | import { PipedreamEnvironment, PipedreamClient } from "@pipedream/sdk" 3 | import { env } from "@/lib/env"; 4 | 5 | const getEnvironment = (): PipedreamEnvironment => { 6 | if (env.PIPEDREAM_API_HOST) { 7 | return `https://${env.PIPEDREAM_API_HOST}` as PipedreamEnvironment; 8 | } 9 | 10 | // Default to Prod for local development (Dev environment requires DEV_NAMESPACE) 11 | return PipedreamEnvironment.Prod; 12 | } 13 | 14 | export const backendClient = () => { 15 | return new PipedreamClient({ 16 | clientId: env.PIPEDREAM_CLIENT_ID, 17 | clientSecret: env.PIPEDREAM_CLIENT_SECRET, 18 | environment: getEnvironment(), 19 | projectEnvironment: env.PIPEDREAM_PROJECT_ENVIRONMENT, 20 | projectId: env.PIPEDREAM_PROJECT_ID, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/Terminal.tsx: -------------------------------------------------------------------------------- 1 | export interface TerminalOutput { 2 | error?: boolean 3 | message?: string 4 | data?: Record 5 | [key: string]: unknown 6 | } 7 | 8 | interface TerminalProps { 9 | shouldAnimate?: boolean 10 | output?: TerminalOutput | null 11 | } 12 | 13 | export const Terminal = ({ shouldAnimate, output }: TerminalProps) => { 14 | return ( 15 |
16 | {shouldAnimate ? ( 17 |
18 |
19 |
20 | ) : output ? ( 21 |
22 |           {JSON.stringify(output, null, 2)}
23 |         
24 | ) : null} 25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | html { 7 | overflow-x: hidden; 8 | width: 100%; 9 | } 10 | body { 11 | width: 100%; 12 | min-height: 100vh; 13 | } 14 | } 15 | 16 | @layer components { 17 | .text-title { 18 | @apply text-2xl font-bold text-gray-800; 19 | } 20 | 21 | .text-subtitle { 22 | @apply text-lg font-semibold text-gray-800; 23 | } 24 | 25 | .text-body { 26 | @apply font-normal text-base text-gray-600; 27 | } 28 | 29 | .text-small { 30 | @apply font-normal text-sm text-gray-600; 31 | } 32 | 33 | .text-code { 34 | @apply font-mono text-sm; 35 | } 36 | 37 | .text-error { 38 | @apply text-red-500; 39 | } 40 | 41 | .text-link { 42 | @apply text-blue-600 hover:underline; 43 | } 44 | } -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/api/pipedream/token/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { serverConnectTokenCreate } from "@/app/server"; 4 | 5 | export async function POST(request: Request) { 6 | try { 7 | const { externalUserId } = await request.json(); 8 | 9 | if (!externalUserId || typeof externalUserId !== "string") { 10 | return NextResponse.json( 11 | { error: "externalUserId is required" }, 12 | { status: 400 } 13 | ); 14 | } 15 | 16 | const tokenResponse = await serverConnectTokenCreate({ externalUserId }); 17 | return NextResponse.json(tokenResponse); 18 | } catch (error) { 19 | console.error("Error creating connect token via API route:", error); 20 | return NextResponse.json( 21 | { error: "Failed to create connect token" }, 22 | { status: 500 } 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /mcp/shared/pd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pd.js","sourceRoot":"","sources":["pd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,MAAM,GAAG,qBAAqB,CACzC,CAAC,CAAC,MAAM,CAAC;IACP,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAE1B,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC/B,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE;IACnC,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE;IAChC,6BAA6B,EAAE,CAAC;SAC7B,IAAI,CAAC,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;SACnC,OAAO,CAAC,aAAa,CAAC;IAEzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC;CACjE,CAAC,CACH,CAAC;AAEF,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;AAExC,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,KAAa,EAAE,EAAE;IAC/C,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC;IAE5C,OAAO;QACL,aAAa,EAAE,UAAU,WAAW,EAAE;QACtC,iBAAiB,EAAE,MAAM,CAAC,oBAAoB;QAC9C,kBAAkB,EAAE,MAAM,CAAC,6BAA6B;QACxD,uBAAuB,EAAE,KAAK;KAC/B,CAAC;AACJ,CAAC,CAAC"} -------------------------------------------------------------------------------- /mcp/ai-sdk/README.md: -------------------------------------------------------------------------------- 1 | # AI SDK Example 2 | 3 | This example demonstrates how to use [Vercel's AI SDK](https://ai-sdk.dev) with Pipedream's dynamic MCP server. 4 | 5 | ## Prerequisites 6 | 7 | See the [main README](../../README.md) for environment setup. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | pnpm install 13 | ``` 14 | 15 | ## Usage 16 | 17 | The `--external-user-id` or `-u` parameter is required. 18 | 19 | ### Basic Usage 20 | 21 | ```bash 22 | pnpm start -u your-user-id "Send a message to Slack saying hello" 23 | ``` 24 | 25 | ### With Additional Options 26 | 27 | ```bash 28 | pnpm start -u your-user-id "Create a Linear ticket for the bug I found" --model gpt-4 --max-steps 15 29 | ``` 30 | 31 | ### Available Options 32 | 33 | - `--external-user-id` or `-u` (required): The external user ID for Pipedream Connect 34 | - `--model`: AI model 35 | - `--max-steps`: Maximum conversation steps (default: 10) 36 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/GoogleAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import Script from "next/script" 2 | 3 | const GA_MEASUREMENT_ID = "AW-770996949" 4 | 5 | export function GoogleAnalytics() { 6 | // Commented out Google Tag Manager 7 | return null 8 | 9 | // if (process.env.NODE_ENV !== "production") { 10 | // return null 11 | // } 12 | 13 | // return ( 14 | // <> 15 | // 28 | // 29 | // ) 30 | } -------------------------------------------------------------------------------- /connect-react-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "react-jsx", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./*" 28 | ] 29 | } 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts", 36 | "next.config.mjs", 37 | ".next/dev/types/**/*.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "./*" 27 | ] 28 | }, 29 | "types": [ 30 | "node", 31 | "@types/node" 32 | ], 33 | "target": "ES2017" 34 | }, 35 | "include": [ 36 | "next-env.d.ts", 37 | "**/*.ts", 38 | "**/*.tsx", 39 | ".next/types/**/*.ts" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "dev-update": "npm update && next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@pipedream/sdk": "^2.0.7", 14 | "next": "15.1.9", 15 | "prismjs": "^1.29.0", 16 | "react": "^19", 17 | "react-dom": "^19" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20", 21 | "@types/prismjs": "^1.26.4", 22 | "@types/react": "^19", 23 | "@types/react-dom": "^19", 24 | "@typescript-eslint/eslint-plugin": "^8.1.0", 25 | "@typescript-eslint/parser": "^8.1.0", 26 | "autoprefixer": "^10.0.1", 27 | "eslint": "^8.57.0", 28 | "eslint-config-next": "^15.1.9", 29 | "postcss": "^8", 30 | "tailwindcss": "^3.3.0", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mcp/openai-sdk/README.md: -------------------------------------------------------------------------------- 1 | # OpenAI SDK Example 2 | 3 | This example demonstrates how to use the [OpenAI SDK](https://github.com/openai/openai-node) with Pipedream's dynamic MCP server. 4 | 5 | ## Prerequisites 6 | 7 | See the [main README](../../README.md) for environment setup. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | pnpm install 13 | ``` 14 | 15 | ## Usage 16 | 17 | The `--external-user-id` or `-u` parameter is required. 18 | 19 | ### Basic Usage 20 | 21 | ```bash 22 | pnpm start -u "Send a message to Slack saying hello" 23 | ``` 24 | 25 | ### With Additional Options 26 | 27 | ```bash 28 | pnpm start -u "Create a Linear ticket for the bug I found" --model gpt-4 --max-steps 15 29 | ``` 30 | 31 | ### Available Options 32 | 33 | - `--external-user-id` or `-u` (required): The external user ID for Pipedream Connect 34 | - `--model`: AI model (default: gpt-4-1106-preview) 35 | - `--max-steps`: Maximum conversation steps (default: 10) 36 | -------------------------------------------------------------------------------- /connect-react-demo/app/actions/backendClient.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { env } from "@/lib/env"; 4 | import { backendClient } from "@/lib/backend-client"; 5 | 6 | export type FetchTokenOpts = { 7 | externalUserId: string 8 | } 9 | 10 | const allowedOrigins = ([ 11 | process.env.VERCEL_URL, 12 | process.env.VERCEL_BRANCH_URL, 13 | process.env.VERCEL_PROJECT_PRODUCTION_URL, 14 | ...env.PIPEDREAM_ALLOWED_ORIGINS, 15 | ].filter(Boolean) as string[]).map((origin) => { 16 | if (origin.startsWith("http")) { 17 | return origin 18 | } 19 | return `https://${origin}` 20 | }); 21 | 22 | export const fetchToken = async (opts: FetchTokenOpts) => { 23 | const serverClient = backendClient() 24 | 25 | const resp = await serverClient.tokens.create({ 26 | externalUserId: opts.externalUserId, 27 | allowedOrigins: allowedOrigins, // TODO set this to the correct origin 28 | webhookUri: process.env.PIPEDREAM_CONNECT_WEBHOOK_URI, 29 | }); 30 | return resp 31 | } 32 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/TabsHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 2 | import { 3 | Tooltip, 4 | TooltipContent, 5 | TooltipProvider, 6 | TooltipTrigger, 7 | } from "@/components/ui/tooltip" 8 | 9 | export const TabsHeader = () => ( 10 | 11 | 12 | Actions 13 | 14 | 15 | 16 | 17 | 18 | Triggers 19 | 20 | 21 | 22 | 23 |

Coming soon

24 |
25 |
26 |
27 |
28 |
29 | ) 30 | -------------------------------------------------------------------------------- /connect-react-demo/lib/hooks/use-copy-to-clipboard.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | 3 | interface UseCopyToClipboardReturn { 4 | copied: boolean 5 | copy: (text: string) => Promise 6 | error: string | null 7 | } 8 | 9 | export const useCopyToClipboard = (timeout = 2000): UseCopyToClipboardReturn => { 10 | const [copied, setCopied] = useState(false) 11 | const [error, setError] = useState(null) 12 | 13 | const copy = useCallback(async (text: string) => { 14 | if (!navigator?.clipboard) { 15 | setError("Clipboard not supported") 16 | return 17 | } 18 | 19 | try { 20 | await navigator.clipboard.writeText(text) 21 | setCopied(true) 22 | setError(null) 23 | 24 | setTimeout(() => setCopied(false), timeout) 25 | } catch (err) { 26 | setError(err instanceof Error ? err.message : "Copy failed") 27 | setCopied(false) 28 | } 29 | }, [timeout]) 30 | 31 | return { copied, copy, error } 32 | } -------------------------------------------------------------------------------- /mcp/shared/pd.js: -------------------------------------------------------------------------------- 1 | import { PipedreamClient } from "@pipedream/sdk"; 2 | import { loadAndValidateConfig } from "./config"; 3 | import { z } from "zod"; 4 | export const config = loadAndValidateConfig(z.object({ 5 | OPENAI_API_KEY: z.string(), 6 | PIPEDREAM_CLIENT_ID: z.string(), 7 | PIPEDREAM_CLIENT_SECRET: z.string(), 8 | PIPEDREAM_PROJECT_ID: z.string(), 9 | PIPEDREAM_PROJECT_ENVIRONMENT: z 10 | .enum(["development", "production"]) 11 | .default("development"), 12 | MCP_HOST: z.string().default("https://remote.mcp.pipedream.net"), 13 | })); 14 | export const pd = new PipedreamClient(); 15 | export const pdHeaders = async (exuid) => { 16 | const accessToken = await pd.rawAccessToken; 17 | return { 18 | Authorization: `Bearer ${accessToken}`, 19 | "x-pd-project-id": config.PIPEDREAM_PROJECT_ID, 20 | "x-pd-environment": config.PIPEDREAM_PROJECT_ENVIRONMENT, 21 | "x-pd-external-user-id": exuid, 22 | }; 23 | }; 24 | //# sourceMappingURL=pd.js.map -------------------------------------------------------------------------------- /mcp/shared/pd.ts: -------------------------------------------------------------------------------- 1 | import { PipedreamClient } from "@pipedream/sdk"; 2 | import { loadAndValidateConfig } from "./config"; 3 | import { z } from "zod"; 4 | 5 | export const config = loadAndValidateConfig( 6 | z.object({ 7 | OPENAI_API_KEY: z.string(), 8 | 9 | PIPEDREAM_CLIENT_ID: z.string(), 10 | PIPEDREAM_CLIENT_SECRET: z.string(), 11 | PIPEDREAM_PROJECT_ID: z.string(), 12 | PIPEDREAM_PROJECT_ENVIRONMENT: z 13 | .enum(["development", "production"]) 14 | .default("development"), 15 | 16 | MCP_HOST: z.string().default("https://remote.mcp.pipedream.net"), 17 | }) 18 | ); 19 | 20 | export const pd = new PipedreamClient(); 21 | 22 | export const pdHeaders = async (exuid: string) => { 23 | const accessToken = await pd.rawAccessToken; 24 | 25 | return { 26 | Authorization: `Bearer ${accessToken}`, 27 | "x-pd-project-id": config.PIPEDREAM_PROJECT_ID, 28 | "x-pd-environment": config.PIPEDREAM_PROJECT_ENVIRONMENT, 29 | "x-pd-external-user-id": exuid, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/AuthSection.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Tooltip, 3 | TooltipContent, 4 | TooltipProvider, 5 | TooltipTrigger, 6 | } from "@/components/ui/tooltip" 7 | 8 | interface AuthSectionProps { 9 | externalUserId: string 10 | } 11 | 12 | export const AuthSection = ({ externalUserId }: AuthSectionProps) => ( 13 |
14 |
15 |
User ID
16 | 17 | 18 | 19 |
20 | {externalUserId} 21 |
22 |
23 | 24 | {externalUserId} 25 | 26 |
27 |
28 |
29 |
30 | ) 31 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/SectionHeader.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { cn } from "@/lib/utils" 4 | import { FC, ReactNode } from "react" 5 | 6 | interface SectionHeaderProps { 7 | title: string 8 | icon?: ReactNode 9 | children?: ReactNode 10 | variant?: "default" | "terminal" 11 | } 12 | 13 | export const SectionHeader: FC = ({ 14 | title, 15 | icon, 16 | children, 17 | variant = "default", 18 | }) => { 19 | return ( 20 |
28 |
29 | {icon} 30 | {title} 31 |
32 | {children} 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/default-unstyled.ts: -------------------------------------------------------------------------------- 1 | import type { CustomizationConfig } from "@pipedream/connect-react" 2 | 3 | const customization: CustomizationConfig = { 4 | styles: { 5 | controlSubmit: (base, { form }) => { 6 | const spinner = `` 7 | return { 8 | ...base, 9 | backgroundImage: form.submitting ? `url('data:image/svg+xml;base64,${btoa(spinner)}')` : "unset", 10 | backgroundPosition: "right 15% top 50%", 11 | backgroundRepeat: "no-repeat", 12 | } 13 | }, 14 | }, 15 | 16 | } 17 | 18 | export default customization 19 | -------------------------------------------------------------------------------- /connect-react-demo/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | basePath: '/connect/demo', 4 | assetPrefix: '/connect/demo', 5 | // Note: eslint config removed in Next.js 16 - use ESLint CLI directly if needed 6 | typescript: { 7 | ignoreBuildErrors: true, 8 | }, 9 | experimental: { 10 | serverActions: { 11 | allowedOrigins: [ 12 | 'pipedream.com', 13 | 'https://pipedream.com', 14 | 'pipedream-connect-demo.vercel.app', 15 | 'https://pipedream-connect-demo.vercel.app' 16 | ] 17 | } 18 | }, 19 | async redirects() { 20 | return [ 21 | { 22 | source: '/', 23 | destination: '/connect/demo', 24 | permanent: false, 25 | basePath: false 26 | } 27 | ] 28 | }, 29 | async rewrites() { 30 | return { 31 | beforeFiles: [ 32 | { 33 | source: '/_vercel/:path*', 34 | destination: 'https://pipedream-connect-demo.vercel.app/_vercel/:path*', 35 | basePath: false 36 | } 37 | ] 38 | } 39 | }, 40 | } 41 | 42 | export default nextConfig 43 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/CustomizationSection.tsx: -------------------------------------------------------------------------------- 1 | import Select from "react-select" 2 | 3 | interface CustomizationSectionProps { 4 | customizationOptions: any[] 5 | customizationOption: any 6 | setCustomizationOption: (option: any) => void 7 | setFileCode: (code: string | undefined) => void 8 | } 9 | 10 | export const CustomizationSection = ({ 11 | customizationOptions, 12 | customizationOption, 13 | setCustomizationOption, 14 | setFileCode, 15 | }: CustomizationSectionProps) => ( 16 |
17 |
18 |
Theme
19 | ({ 64 | label: prop.name, 65 | value: prop.name, 66 | }))} 67 | isMulti={true} 68 | value={propNames.map((name) => ({ 69 | label: name, 70 | value: name, 71 | }))} 72 | onChange={(vs) => setPropNames(vs.map((v) => v.value))} 73 | className="w-full" 74 | classNamePrefix="react-select" 75 | components={{ 76 | IndicatorSeparator: () => null, 77 | }} 78 | /> 79 |
80 | )} 81 |
82 |
83 | ) 84 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/blue-theme.ts: -------------------------------------------------------------------------------- 1 | import { CustomizationConfig } from "@pipedream/connect-react" 2 | import { CustomDropdownIndicator } from "./CustomIndicators" 3 | import { CustomLabel } from "./CustomLabel" 4 | 5 | const customization: CustomizationConfig = { 6 | components: { 7 | Label: CustomLabel, 8 | controlSelect: { 9 | DropdownIndicator: CustomDropdownIndicator, 10 | }, 11 | }, 12 | styles: { 13 | label: { fontSize: "80%" }, 14 | controlInput: (base, { theme }) => ({ 15 | ...base, 16 | borderTop: 0, 17 | borderLeft: 0, 18 | borderRight: 0, 19 | border: "solid", 20 | borderColor: theme.colors.primary, 21 | backgroundColor: theme.colors.neutral0, 22 | }), 23 | description: { fontSize: "60%" }, 24 | field: { padding: 2 }, 25 | heading: { fontSize: "80%" }, 26 | controlSelect: { 27 | control: (base, { theme }) => ({ 28 | ...base, 29 | borderRadius: 0, 30 | borderColor: theme.colors.primary25, 31 | fontSize: "small", 32 | maxHeight: "36px", 33 | }), 34 | container: (base) => ({ 35 | ...base, 36 | fontSize: "small", 37 | }), 38 | menu: (base) => ({ 39 | ...base, 40 | fontSize: "small", 41 | }), 42 | }, 43 | controlSubmit: (base, { form, theme }) => { 44 | const spinner = `` 45 | return { 46 | ...base, 47 | backgroundImage: form.submitting ? `url('data:image/svg+xml;base64,${btoa(spinner)}')` : "unset", 48 | backgroundPosition: "right 15% top 50%", 49 | backgroundRepeat: "no-repeat", 50 | } 51 | }, 52 | }, 53 | theme: { 54 | borderRadius: 0, 55 | colors: { 56 | primary: "hsl(200, 100%, 60%)", 57 | primary75: "hsl(200, 100%, 55%)", 58 | primary50: "hsl(200, 100%, 40%)", 59 | primary25: "hsl(200, 100%, 35%)", 60 | 61 | danger: "#DE350B", 62 | dangerLight: "#FFBDAD", 63 | 64 | neutral0: "hsl(200, 50%, 97%)", 65 | neutral5: "hsl(200, 50%, 95%)", 66 | neutral10: "hsl(200, 50%, 90%)", 67 | neutral20: "hsl(200, 50%, 80%)", 68 | neutral30: "hsl(200, 50%, 70%)", 69 | neutral40: "hsl(200, 50%, 60%)", 70 | neutral50: "hsl(200, 50%, 50%)", 71 | neutral60: "hsl(200, 50%, 40%)", 72 | neutral70: "hsl(200, 50%, 30%)", 73 | neutral80: "hsl(200, 50%, 20%)", 74 | neutral90: "hsl(200, 50%, 10%)", 75 | }, 76 | spacing: { 77 | baseUnit: 4, 78 | controlHeight: 10, 79 | menuGutter: 6, 80 | }, 81 | }, 82 | } 83 | 84 | export default customization 85 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/TerminalCollapsible.tsx: -------------------------------------------------------------------------------- 1 | import { IoTerminalOutline } from "react-icons/io5" 2 | import { 3 | Collapsible, 4 | CollapsibleContent, 5 | CollapsibleTrigger, 6 | } from "@/components/ui/collapsible" 7 | import { Terminal } from "./Terminal" 8 | import type { TerminalOutput } from "./Terminal" 9 | import { SectionHeader } from "./SectionHeader" 10 | import { cn } from "@/lib/utils" 11 | 12 | interface TerminalCollapsibleProps { 13 | isOpen: boolean 14 | onOpenChange: (open: boolean) => void 15 | hasOutput: boolean 16 | output?: TerminalOutput | null 17 | } 18 | 19 | export const TerminalCollapsible = ({ 20 | isOpen, 21 | onOpenChange, 22 | hasOutput, 23 | output, 24 | }: TerminalCollapsibleProps) => { 25 | const getStatusMessage = () => { 26 | if (!hasOutput) return "Waiting for submission..." 27 | return "Response received" 28 | } 29 | 30 | return ( 31 | 32 | 33 | } 36 | variant="terminal" 37 | > 38 |
39 | 43 | 53 | {getStatusMessage()} 54 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 | 63 |
64 | 65 | 66 | ) 67 | } 68 | 69 | const StatusIndicator = ({ 70 | active, 71 | success = false, 72 | }: { 73 | active: boolean 74 | success?: boolean 75 | }) => ( 76 |
77 |
87 | {active && ( 88 |
95 | )} 96 |
97 | ) 98 | -------------------------------------------------------------------------------- /mcp/shared/cli.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,6BAA6B,EAAE,MAAM,iDAAiD,CAAC;AAchG,MAAM,CAAC,MAAM,aAAa,GAAG;;;;iGAIoE,CAAC;AAElG,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,WAAmB;IACjE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO,OAAO;SACX,IAAI,CAAC,IAAI,CAAC;SACV,WAAW,CAAC,WAAW,CAAC;SACxB,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CAAC,eAAe,EAAE,4BAA4B,CAAC;SACvD,cAAc,CACb,iCAAiC,EACjC,6BAA6B,CAC9B;SACA,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,EAAE,OAAO,CAAC;SAC7D,MAAM,CAAC,yBAAyB,EAAE,4BAA4B,EAAE,IAAI,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,WAAmB,EACnB,OAAuB;IAEvB,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO;QACL,WAAW;QACX,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,gBAAwB;IAC/D,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,GAAG,OAAO,gBAAgB,EAAE,CAAC,CAAC;IAEtE,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC,MAAM,EAAE;QAC1D,WAAW,EAAE;YACX,OAAO;SACR;KACF,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAwB,EAAE,OAAe;IAC1E,OAAO,CAAC,GAAG,CAAC,mBAAmB,OAAO,qBAAqB,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,+BAA+B,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC;WACH,MAAM,CAAC,OAAO,CAAC,KAAK;eAChB,MAAM,CAAC,QAAQ;aACjB,QAAQ,CAAC,QAAQ;CAC7B,CAAC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,WAAmB,EAAE,QAAgB;IAC3D,OAAO,CAAC,GAAG,CAAC,WAAW,WAAW,IAAI,QAAQ,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,CAAC,GAAG,CAAC,uBAAuB,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,CAAC,GAAG,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,OAAO,CAAC,GAAG,CAAC,8BAA8B,QAAQ,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QAC/B,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,OAAe;IACzD,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,wBAAwB,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"} -------------------------------------------------------------------------------- /connect-react-demo/app/components/DemoHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { 3 | DropdownMenu, 4 | DropdownMenuContent, 5 | DropdownMenuItem, 6 | DropdownMenuTrigger, 7 | } from "@/components/ui/dropdown-menu" 8 | import { PipedreamLogo } from "./PipedreamLogo" 9 | import { IoHelpCircleOutline, IoMenuOutline } from "react-icons/io5" 10 | import { SiGithub } from "react-icons/si" 11 | 12 | const NAV_LINKS = [ 13 | { href: "https://pipedream.com/support", label: "Contact us", icon: IoHelpCircleOutline }, 14 | { href: "https://pipedream.com/docs/connect/components", label: "Read the docs", icon: "📖" }, 15 | { href: "https://github.com/PipedreamHQ/pipedream-connect-examples/tree/master/connect-react-demo", label: "View on GitHub", icon: SiGithub }, 16 | ] 17 | 18 | export function DemoHeader() { 19 | return ( 20 |
21 |
22 | 23 |
24 | 25 |
26 | 33 | 41 | 46 |
47 | 48 |
49 | 50 | 51 | 55 | 56 | 57 | {NAV_LINKS.map((link) => ( 58 | window.open(link.href, "_blank")} 61 | className="justify-start" 62 | > 63 | {typeof link.icon === "string" ? ( 64 |
65 | {link.icon} 66 |
67 | ) : ( 68 | 69 | )} 70 | {link.label} 71 |
72 | ))} 73 |
74 |
75 |
76 |
77 | ) 78 | } -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/_hooks/useAppsSearch.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useRef, useState } from "react"; 4 | import type { App, PipedreamClient as FrontendClient } from "@pipedream/sdk/browser"; 5 | 6 | interface UseAppsSearchResult { 7 | results: App[]; 8 | isSearching: boolean; 9 | isLoadingMore: boolean; 10 | hasMore: boolean; 11 | currentQuery?: string; 12 | searchApps: (query?: string) => Promise; 13 | loadMore: () => Promise; 14 | reset: () => void; 15 | } 16 | 17 | export function useAppsSearch(client: FrontendClient | null): UseAppsSearchResult { 18 | const [results, setResults] = useState([]); 19 | const [isSearching, setIsSearching] = useState(false); 20 | const [isLoadingMore, setIsLoadingMore] = useState(false); 21 | const [hasMore, setHasMore] = useState(true); 22 | const [currentQuery, setCurrentQuery] = useState(undefined); 23 | 24 | const pageRef = useRef> | null>(null); 25 | 26 | const runSearch = useCallback( 27 | async (query?: string, append = false) => { 28 | if (!client) { 29 | return; 30 | } 31 | 32 | if (append) { 33 | setIsLoadingMore(true); 34 | } else { 35 | setIsSearching(true); 36 | pageRef.current = null; 37 | } 38 | 39 | try { 40 | if (!append) { 41 | const page = await client.apps.list({ 42 | q: query, 43 | limit: 20, 44 | sortKey: "featured_weight", 45 | sortDirection: "desc", 46 | }); 47 | pageRef.current = page; 48 | } else { 49 | const page = pageRef.current; 50 | if (!page) return; 51 | if (!page.hasNextPage()) { 52 | setHasMore(false); 53 | return; 54 | } 55 | await page.getNextPage(); 56 | } 57 | 58 | const page = pageRef.current; 59 | if (!page) return; 60 | 61 | const filtered = page.data.filter((app) => app.authType !== null); 62 | 63 | setResults((prev) => { 64 | if (append) { 65 | const existingIds = new Set(prev.map((app) => app.nameSlug)); 66 | const merged = [...prev, ...filtered.filter((app) => !existingIds.has(app.nameSlug))]; 67 | return merged; 68 | } 69 | return filtered; 70 | }); 71 | 72 | setHasMore(page.hasNextPage()); 73 | setCurrentQuery(query); 74 | } finally { 75 | if (append) { 76 | setIsLoadingMore(false); 77 | } else { 78 | setIsSearching(false); 79 | } 80 | } 81 | }, 82 | [client], 83 | ); 84 | 85 | const reset = useCallback(() => { 86 | pageRef.current = null; 87 | setResults([]); 88 | setHasMore(true); 89 | setCurrentQuery(undefined); 90 | }, []); 91 | 92 | const loadMore = useCallback(async () => { 93 | await runSearch(currentQuery, true); 94 | }, [currentQuery, runSearch]); 95 | 96 | return { 97 | results, 98 | isSearching, 99 | isLoadingMore, 100 | hasMore, 101 | currentQuery, 102 | searchApps: runSearch, 103 | loadMore, 104 | reset, 105 | }; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/_hooks/useConnectToken.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback, useEffect, useRef, useState } from "react"; 4 | import type { PipedreamClient as FrontendClient } from "@pipedream/sdk/browser"; 5 | import type { ConnectContext } from "../types"; 6 | 7 | const frontendHost = process.env.NEXT_PUBLIC_PIPEDREAM_FRONTEND_HOST || "pipedream.com"; 8 | const apiHost = process.env.NEXT_PUBLIC_PIPEDREAM_API_HOST || "api.pipedream.com"; 9 | 10 | interface RefreshResult { 11 | token: string; 12 | expiresAt: Date; 13 | connectLinkUrl: string | null; 14 | } 15 | 16 | export function useConnectToken(initialContext: ConnectContext) { 17 | const [token, setToken] = useState(initialContext.token); 18 | const [connectLinkUrl, setConnectLinkUrl] = useState(initialContext.connectLinkUrl); 19 | const [expiresAt, setExpiresAt] = useState(new Date(initialContext.expiresAt)); 20 | const [client, setClient] = useState(null); 21 | 22 | const tokenRef = useRef(token); 23 | const expiresAtRef = useRef(expiresAt); 24 | 25 | useEffect(() => { 26 | tokenRef.current = token; 27 | expiresAtRef.current = expiresAt; 28 | }, [token, expiresAt]); 29 | 30 | const refreshToken = useCallback(async (): Promise => { 31 | const res = await fetch("/api/pipedream/token", { 32 | method: "POST", 33 | headers: { "content-type": "application/json" }, 34 | body: JSON.stringify({ externalUserId: initialContext.externalUserId }), 35 | }); 36 | 37 | if (!res.ok) { 38 | throw new Error("Failed to refresh connect token"); 39 | } 40 | 41 | const response = await res.json(); 42 | const nextExpiresAt = new Date(response.expiresAt); 43 | 44 | setToken(response.token); 45 | setConnectLinkUrl(response.connectLinkUrl ?? null); 46 | setExpiresAt(nextExpiresAt); 47 | 48 | return { 49 | token: response.token, 50 | expiresAt: nextExpiresAt, 51 | connectLinkUrl: response.connectLinkUrl ?? null, 52 | }; 53 | }, [initialContext.externalUserId]); 54 | 55 | useEffect(() => { 56 | if (!initialContext.externalUserId || !token || client) { 57 | return; 58 | } 59 | 60 | let isMounted = true; 61 | 62 | const loadClient = async () => { 63 | const { createFrontendClient } = await import("@pipedream/sdk/browser"); 64 | if (!isMounted) return; 65 | 66 | const sdkClient = createFrontendClient({ 67 | frontendHost, 68 | apiHost, 69 | externalUserId: initialContext.externalUserId, 70 | token, 71 | tokenCallback: async () => { 72 | const currentToken = tokenRef.current; 73 | const currentExpiresAt = expiresAtRef.current; 74 | 75 | if (currentToken && currentExpiresAt && currentExpiresAt > new Date()) { 76 | return { token: currentToken, expiresAt: currentExpiresAt }; 77 | } 78 | 79 | const refreshed = await refreshToken(); 80 | return { token: refreshed.token, expiresAt: refreshed.expiresAt }; 81 | }, 82 | }); 83 | 84 | setClient(sdkClient); 85 | }; 86 | 87 | loadClient(); 88 | 89 | return () => { 90 | isMounted = false; 91 | }; 92 | }, [initialContext.externalUserId, token, client, refreshToken]); 93 | 94 | return { 95 | client, 96 | connectLinkUrl, 97 | refreshToken, 98 | token, 99 | expiresAt, 100 | }; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /connect-react-demo/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* force to light mode for now (remove this if we support dark mode everywhere in demo) 6 | @media (prefers-color-scheme: dark) { 7 | :root { 8 | --background: #0a0a0a; 9 | --foreground: #ededed; 10 | } 11 | } 12 | */ 13 | 14 | html, 15 | body { 16 | overscroll-behavior: none; 17 | } 18 | 19 | body { 20 | font-family: system-ui, Arial, Helvetica, sans-serif; 21 | } 22 | 23 | .xstr { 24 | color: #032F62; 25 | } 26 | .xcomment { 27 | color: #888; 28 | } 29 | .xkey { 30 | color: #D73A49; 31 | } 32 | .xtag { 33 | /* color: #22863A; - html element */ 34 | color: #005CC5; 35 | } 36 | .xattr { 37 | color: #6F42C1; 38 | } 39 | .xeq { 40 | color: #D73A49; 41 | } 42 | @layer base { 43 | :root { 44 | --background: 0 0% 100%; 45 | --foreground: 240 10% 3.9%; 46 | --card: 0 0% 100%; 47 | --card-foreground: 240 10% 3.9%; 48 | --popover: 0 0% 100%; 49 | --popover-foreground: 240 10% 3.9%; 50 | --primary: 240 5.9% 10%; 51 | --primary-foreground: 0 0% 98%; 52 | --secondary: 240 4.8% 95.9%; 53 | --secondary-foreground: 240 5.9% 10%; 54 | --muted: 240 4.8% 95.9%; 55 | --muted-foreground: 240 3.8% 46.1%; 56 | --accent: 240 4.8% 95.9%; 57 | --accent-foreground: 240 5.9% 10%; 58 | --destructive: 0 84.2% 60.2%; 59 | --destructive-foreground: 0 0% 98%; 60 | --border: 240 5.9% 90%; 61 | --input: 240 5.9% 90%; 62 | --ring: 240 10% 3.9%; 63 | --chart-1: 12 76% 61%; 64 | --chart-2: 173 58% 39%; 65 | --chart-3: 197 37% 24%; 66 | --chart-4: 43 74% 66%; 67 | --chart-5: 27 87% 67%; 68 | --radius: 0.375rem; 69 | --sidebar-background: 0 0% 100%; 70 | --sidebar-foreground: 240 5.3% 26.1%; 71 | --sidebar-primary: 240 5.9% 10%; 72 | --sidebar-primary-foreground: 0 0% 98%; 73 | --sidebar-accent: 240 4.8% 95.9%; 74 | --sidebar-accent-foreground: 240 5.9% 10%; 75 | --sidebar-border: 220 13% 91%; 76 | --sidebar-ring: 217.2 91.2% 59.8%; 77 | } 78 | .dark { 79 | --background: 240 10% 3.9%; 80 | --foreground: 0 0% 98%; 81 | --card: 240 10% 3.9%; 82 | --card-foreground: 0 0% 98%; 83 | --popover: 240 10% 3.9%; 84 | --popover-foreground: 0 0% 98%; 85 | --primary: 0 0% 98%; 86 | --primary-foreground: 240 5.9% 10%; 87 | --secondary: 240 3.7% 15.9%; 88 | --secondary-foreground: 0 0% 98%; 89 | --muted: 240 3.7% 15.9%; 90 | --muted-foreground: 240 5% 64.9%; 91 | --accent: 240 3.7% 15.9%; 92 | --accent-foreground: 0 0% 98%; 93 | --destructive: 0 62.8% 30.6%; 94 | --destructive-foreground: 0 0% 98%; 95 | --border: 240 3.7% 15.9%; 96 | --input: 240 3.7% 15.9%; 97 | --ring: 240 4.9% 83.9%; 98 | --chart-1: 220 70% 50%; 99 | --chart-2: 160 60% 45%; 100 | --chart-3: 30 80% 55%; 101 | --chart-4: 280 65% 60%; 102 | --chart-5: 340 75% 55%; 103 | --sidebar-background: 240 5.9% 10%; 104 | --sidebar-foreground: 240 4.8% 95.9%; 105 | --sidebar-primary: 224.3 76.3% 48%; 106 | --sidebar-primary-foreground: 0 0% 100%; 107 | --sidebar-accent: 240 3.7% 15.9%; 108 | --sidebar-accent-foreground: 240 4.8% 95.9%; 109 | --sidebar-border: 240 3.7% 15.9%; 110 | --sidebar-ring: 217.2 91.2% 59.8%; 111 | } 112 | } 113 | @layer base { 114 | * { 115 | @apply border-border; 116 | } 117 | body { 118 | @apply bg-background text-foreground; 119 | } 120 | } 121 | 122 | .ide-tab { 123 | position: relative; 124 | border-top-left-radius: 6px; 125 | border-top-right-radius: 6px; 126 | border-bottom-left-radius: 0; 127 | border-bottom-right-radius: 0; 128 | margin-right: 2px; 129 | padding: 0 12px; 130 | height: 32px; 131 | background: #fff; 132 | border: 1px solid #e5e7eb; 133 | border-bottom: none; 134 | } 135 | 136 | .ide-tab::after { 137 | content: ''; 138 | position: absolute; 139 | bottom: -1px; 140 | left: 0; 141 | right: 0; 142 | height: 1px; 143 | background: #fff; 144 | } 145 | 146 | .ide-tab:not(.inactive):hover { 147 | background: #fff !important; 148 | } 149 | 150 | .ide-tab.inactive { 151 | background: #f9fafb; 152 | border-color: transparent; 153 | } 154 | 155 | .ide-tab.inactive::after { 156 | display: none; 157 | } 158 | -------------------------------------------------------------------------------- /mcp/shared/cli.js: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import { config as pdConfig, pdHeaders } from "./pd.js"; 3 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"; 4 | export const SYSTEM_PROMPT = `You are an intelligent AI assistant that can use Pipedream tools to help users. 5 | 6 | Use the available tools to fulfill the user's request effectively. 7 | 8 | If you encounter any errors or need clarification, explain what happened and suggest next steps.`; 9 | export function createBaseProgram(name, description) { 10 | const program = new Command(); 11 | return program 12 | .name(name) 13 | .description(description) 14 | .version("1.0.0") 15 | .argument("", "The instruction to process") 16 | .requiredOption("-u, --external_user_id ", "External user ID (required)") 17 | .option("-m, --model ", "OpenAI model to use", "gpt-5") 18 | .option("-s, --max-steps ", "Maximum conversation steps", "10"); 19 | } 20 | export function validateAndParseOptions(instruction, options) { 21 | const maxSteps = parseInt(options.maxSteps.toString()); 22 | if (isNaN(maxSteps)) { 23 | console.error("❌ max-steps must be a number"); 24 | process.exit(1); 25 | } 26 | return { 27 | instruction, 28 | options, 29 | maxSteps, 30 | }; 31 | } 32 | export async function createMCPTransport(external_user_id) { 33 | console.log("🔧 Setting up MCP transport..."); 34 | const headers = await pdHeaders(external_user_id); 35 | const mcpUrl = new URL(pdConfig.MCP_HOST + `/v1/${external_user_id}`); 36 | const transport = new StreamableHTTPClientTransport(mcpUrl, { 37 | requestInit: { 38 | headers, 39 | }, 40 | }); 41 | return transport; 42 | } 43 | export function logProcessingStart(config, sdkName) { 44 | console.log(`🤖 Initializing ${sdkName} with MCP client...`); 45 | console.log(`🎯 Processing instruction: "${config.instruction}"`); 46 | console.log(`📋 Configuration: 47 | - Model: ${config.options.model} 48 | - Max Steps: ${config.maxSteps} 49 | - MCP URL: ${pdConfig.MCP_HOST} 50 | `); 51 | console.log("📝 Starting conversation loop...\n"); 52 | } 53 | export function logStep(currentStep, maxSteps) { 54 | console.log(`📍 Step ${currentStep}/${maxSteps}`); 55 | } 56 | export function logToolsLoading() { 57 | console.log("🔧 Loading tools from MCP server..."); 58 | } 59 | export function logAvailableTools(toolNames) { 60 | console.log(`📋 Available tools: ${toolNames || "none"}`); 61 | } 62 | export function logAIResponse() { 63 | console.log("🧠 Generating AI response..."); 64 | } 65 | export function logResponse(content) { 66 | console.log(`✨ Response: ${content}`); 67 | } 68 | export function logToolCalls() { 69 | console.log("🔨 Tool calls made:"); 70 | } 71 | export function logToolResults() { 72 | console.log("📊 Tool results:"); 73 | } 74 | export function logExecutingTools() { 75 | console.log("📊 Executing tool calls..."); 76 | } 77 | export function logConversationComplete() { 78 | console.log("✅ Conversation completed successfully"); 79 | } 80 | export function logMaxStepsReached(maxSteps) { 81 | console.log(`⚠️ Reached maximum steps (${maxSteps})`); 82 | } 83 | export function logSessionComplete() { 84 | console.log("\n🎉 Session complete!"); 85 | } 86 | export function logClosingClient() { 87 | console.log("🧹 Closing MCP client..."); 88 | } 89 | export function logClientClosed() { 90 | console.log("✅ MCP client closed"); 91 | } 92 | export function logContinuing() { 93 | console.log("⏳ Continuing to next step...\n"); 94 | } 95 | export function setupGracefulShutdown() { 96 | process.on("SIGINT", async () => { 97 | console.log("\n🛑 Received SIGINT, shutting down gracefully..."); 98 | process.exit(0); 99 | }); 100 | process.on("SIGTERM", async () => { 101 | console.log("\n🛑 Received SIGTERM, shutting down gracefully..."); 102 | process.exit(0); 103 | }); 104 | } 105 | export function handleError(error, sdkName) { 106 | console.log("Error", error); 107 | console.error(`💥 Error occurred in ${sdkName}:`, error); 108 | process.exit(1); 109 | } 110 | //# sourceMappingURL=cli.js.map -------------------------------------------------------------------------------- /connect-react-demo/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as DialogPrimitive from "@radix-ui/react-dialog"; 5 | import { IoClose } from "react-icons/io5"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Dialog = DialogPrimitive.Root; 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger; 12 | 13 | const DialogPortal = DialogPrimitive.Portal; 14 | 15 | const DialogClose = DialogPrimitive.Close; 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )); 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )); 54 | DialogContent.displayName = DialogPrimitive.Content.displayName; 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ); 68 | DialogHeader.displayName = "DialogHeader"; 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ); 82 | DialogFooter.displayName = "DialogFooter"; 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )); 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )); 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | }; 123 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/CodeSection.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { cn } from "@/lib/utils" 3 | import { IoCopyOutline, IoCheckmarkOutline, IoLogoReact } from "react-icons/io5" 4 | import { SiTypescript } from "react-icons/si" 5 | import SyntaxHighlighter from "react-syntax-highlighter" 6 | import { githubGist } from "react-syntax-highlighter/dist/esm/styles/hljs" 7 | import { useState } from "react" 8 | 9 | const syntaxHighlighterTheme = { 10 | ...githubGist, 11 | 'pre[class*="language-"]': { 12 | ...githubGist['pre[class*="language-"]'], 13 | fontSize: "13px", 14 | margin: 0, 15 | padding: "16px", 16 | background: "#FAFAFA", 17 | border: "none", 18 | }, 19 | 'code[class*="language-"]': { 20 | ...githubGist['code[class*="language-"]'], 21 | fontSize: "13px", 22 | fontFamily: 23 | 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 24 | }, 25 | } 26 | 27 | interface CodeSectionProps { 28 | fileCode: string 29 | setFileCode: (code: string | undefined) => void 30 | code: string 31 | customizationOption: any 32 | formControls: React.ReactNode 33 | } 34 | 35 | export const CodeSection = ({ 36 | fileCode, 37 | setFileCode, 38 | code, 39 | customizationOption, 40 | formControls, 41 | }: CodeSectionProps) => { 42 | const [copied, setCopied] = useState(false) 43 | 44 | const handleCopy = async () => { 45 | await navigator.clipboard.writeText(fileCode || code) 46 | setCopied(true) 47 | setTimeout(() => setCopied(false), 1000) 48 | } 49 | 50 | return ( 51 |
52 |
53 | 66 | {customizationOption.file && ( 67 | 80 | )} 81 |
82 | 103 |
104 | 105 |
106 | 123 | {fileCode || code} 124 | 125 |
126 | 127 |
{formControls}
128 |
129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/LiveCodePanel.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { useAppState } from "@/lib/app-state" 5 | import { cn } from "@/lib/utils" 6 | import { CodeBlock } from "./ui/code-block" 7 | import { 8 | generateComponentCode, 9 | generateSetupCode, 10 | generateApiCode, 11 | CODE_FILES 12 | } from "@/lib/code-templates" 13 | 14 | export function LiveCodePanel() { 15 | const { 16 | selectedComponentType, 17 | selectedComponentKey, 18 | externalUserId, 19 | configuredProps, 20 | webhookUrl, 21 | selectedApp, 22 | hideOptionalProps, 23 | enableDebugging, 24 | propNames 25 | } = useAppState() 26 | 27 | const [activeTab, setActiveTab] = useState("current") 28 | const [showLiveUpdates, setShowLiveUpdates] = useState(true) 29 | 30 | const currentComponentCode = generateComponentCode({ 31 | externalUserId, 32 | componentKey: selectedComponentKey, 33 | configuredProps, 34 | selectedComponentType, 35 | webhookUrl, 36 | hideOptionalProps, 37 | enableDebugging, 38 | propNames 39 | }) 40 | 41 | const setupCode = generateSetupCode(externalUserId) 42 | 43 | const apiCode = generateApiCode(externalUserId) 44 | 45 | const files = CODE_FILES 46 | 47 | const getCodeForFile = (fileId: string) => { 48 | switch (fileId) { 49 | case "current": return currentComponentCode 50 | case "setup": return setupCode 51 | case "api": return apiCode 52 | default: return "" 53 | } 54 | } 55 | 56 | return ( 57 |
58 | {/* Header section - fixed height */} 59 |
60 |
61 |

Implementation Code

62 |

63 | {files.find(f => f.id === activeTab)?.description || "Ready-to-use React files - copy these into your project to add Pipedream Connect"} 64 |

65 |
66 | 67 | {/* IDE-style file tabs */} 68 |
69 | {files.map((file, index) => ( 70 | 86 | ))} 87 |
88 |
89 | 90 | {/* Code section */} 91 |
92 | 93 | {getCodeForFile(activeTab)} 94 | 95 | 96 | {activeTab === "current" && selectedComponentKey && showLiveUpdates && ( 97 |
98 |
99 |
100 |
101 |

Live Updates

102 |

103 | This code updates in real-time as you configure the component in the demo. 104 |

105 |
106 | 115 |
116 |
117 | )} 118 |
119 |
120 | ) 121 | } -------------------------------------------------------------------------------- /mcp/shared/cli.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import { config as pdConfig, pdHeaders } from "./pd.js"; 3 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp"; 4 | 5 | export interface ProgramOptions { 6 | model: string; 7 | maxSteps: number; 8 | external_user_id: string; 9 | } 10 | 11 | export interface ProcessingConfig { 12 | instruction: string; 13 | options: ProgramOptions; 14 | maxSteps: number; 15 | } 16 | 17 | export const SYSTEM_PROMPT = `You are an intelligent AI assistant that can use Pipedream tools to help users. 18 | 19 | Use the available tools to fulfill the user's request effectively. 20 | 21 | If you encounter any errors or need clarification, explain what happened and suggest next steps.`; 22 | 23 | export function createBaseProgram(name: string, description: string) { 24 | const program = new Command(); 25 | 26 | return program 27 | .name(name) 28 | .description(description) 29 | .version("1.0.0") 30 | .argument("", "The instruction to process") 31 | .requiredOption( 32 | "-u, --external_user_id ", 33 | "External user ID (required)" 34 | ) 35 | .option("-m, --model ", "OpenAI model to use", "gpt-5") 36 | .option("-s, --max-steps ", "Maximum conversation steps", "10"); 37 | } 38 | 39 | export function validateAndParseOptions( 40 | instruction: string, 41 | options: ProgramOptions 42 | ): ProcessingConfig { 43 | const maxSteps = parseInt(options.maxSteps.toString()); 44 | if (isNaN(maxSteps)) { 45 | console.error("❌ max-steps must be a number"); 46 | process.exit(1); 47 | } 48 | 49 | return { 50 | instruction, 51 | options, 52 | maxSteps, 53 | }; 54 | } 55 | 56 | export async function createMCPTransport(external_user_id: string) { 57 | console.log("🔧 Setting up MCP transport..."); 58 | 59 | const headers = await pdHeaders(external_user_id); 60 | const mcpUrl = new URL(pdConfig.MCP_HOST + `/v1/${external_user_id}`); 61 | 62 | const transport = new StreamableHTTPClientTransport(mcpUrl, { 63 | requestInit: { 64 | headers, 65 | }, 66 | }); 67 | 68 | return transport; 69 | } 70 | 71 | export function logProcessingStart(config: ProcessingConfig, sdkName: string) { 72 | console.log(`🤖 Initializing ${sdkName} with MCP client...`); 73 | console.log(`🎯 Processing instruction: "${config.instruction}"`); 74 | console.log(`📋 Configuration: 75 | - Model: ${config.options.model} 76 | - Max Steps: ${config.maxSteps} 77 | - MCP URL: ${pdConfig.MCP_HOST} 78 | `); 79 | console.log("📝 Starting conversation loop...\n"); 80 | } 81 | 82 | export function logStep(currentStep: number, maxSteps: number) { 83 | console.log(`📍 Step ${currentStep}/${maxSteps}`); 84 | } 85 | 86 | export function logToolsLoading() { 87 | console.log("🔧 Loading tools from MCP server..."); 88 | } 89 | 90 | export function logAvailableTools(toolNames: string) { 91 | console.log(`📋 Available tools: ${toolNames || "none"}`); 92 | } 93 | 94 | export function logAIResponse() { 95 | console.log("🧠 Generating AI response..."); 96 | } 97 | 98 | export function logResponse(content: string) { 99 | console.log(`✨ Response: ${content}`); 100 | } 101 | 102 | export function logToolCalls() { 103 | console.log("🔨 Tool calls made:"); 104 | } 105 | 106 | export function logToolResults() { 107 | console.log("📊 Tool results:"); 108 | } 109 | 110 | export function logExecutingTools() { 111 | console.log("📊 Executing tool calls..."); 112 | } 113 | 114 | export function logConversationComplete() { 115 | console.log("✅ Conversation completed successfully"); 116 | } 117 | 118 | export function logMaxStepsReached(maxSteps: number) { 119 | console.log(`⚠️ Reached maximum steps (${maxSteps})`); 120 | } 121 | 122 | export function logSessionComplete() { 123 | console.log("\n🎉 Session complete!"); 124 | } 125 | 126 | export function logClosingClient() { 127 | console.log("🧹 Closing MCP client..."); 128 | } 129 | 130 | export function logClientClosed() { 131 | console.log("✅ MCP client closed"); 132 | } 133 | 134 | export function logContinuing() { 135 | console.log("⏳ Continuing to next step...\n"); 136 | } 137 | 138 | export function setupGracefulShutdown() { 139 | process.on("SIGINT", async () => { 140 | console.log("\n🛑 Received SIGINT, shutting down gracefully..."); 141 | process.exit(0); 142 | }); 143 | 144 | process.on("SIGTERM", async () => { 145 | console.log("\n🛑 Received SIGTERM, shutting down gracefully..."); 146 | process.exit(0); 147 | }); 148 | } 149 | 150 | export function handleError(error: unknown, sdkName: string) { 151 | console.log("Error", error); 152 | console.error(`💥 Error occurred in ${sdkName}:`, error); 153 | process.exit(1); 154 | } 155 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/_components/ConnectOptions.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import CodePanel from "./CodePanel"; 4 | import { ExternalLink } from "./ExternalLink"; 5 | import type { GetAppResponse } from "@pipedream/sdk/browser"; 6 | 7 | interface ConnectOptionsProps { 8 | selectedApp: GetAppResponse; 9 | externalUserId: string; 10 | connectAccount: () => Promise; 11 | accountId: string | null; 12 | accountName: string | null; 13 | connectLinkUrl: string | null; 14 | frontendSDKDocs: string; 15 | accountsDocsUrl: string; 16 | projectId?: string; 17 | } 18 | 19 | export function ConnectOptions({ 20 | selectedApp, 21 | externalUserId, 22 | connectAccount, 23 | accountId, 24 | accountName, 25 | connectLinkUrl, 26 | frontendSDKDocs, 27 | accountsDocsUrl, 28 | projectId, 29 | }: ConnectOptionsProps) { 30 | return ( 31 |
32 |

Connect your account

33 |
34 |

Option 1: Connect via SDK

35 |

36 | Use the frontend SDK to open a Pipedream iFrame directly from your site ( 37 | see docs). 38 |

39 | 42 | {accountId && ( 43 |
44 |
45 | ✅ Account connected!{" "} 46 | {accountName ? ( 47 | <> 48 | {accountName} (ID: 49 | {accountId} 50 | ) 51 | 52 | ) : ( 53 | <> 54 | Account ID: {accountId} 55 | 56 | )} 57 |
58 |
59 | You can manage users and their connected accounts in the 60 | {projectId ? ( 61 | 62 | Pipedream UI 63 | 64 | ) : ( 65 | Pipedream UI 66 | )} 67 | or 68 | via the API 69 |
70 |
71 | )} 72 |

73 | Call connectAccount() with the token and the app slug of the app you'd like to connect: 74 |

75 | { 84 | const res = await fetch("/api/pipedream/token", { 85 | method: "POST", 86 | headers: { "content-type": "application/json" }, 87 | body: JSON.stringify({ externalUserId }), 88 | }); 89 | 90 | if (!res.ok) { 91 | throw new Error("Failed to fetch connect token"); 92 | } 93 | 94 | const { token, expiresAt } = await res.json(); 95 | return { token, expiresAt: new Date(expiresAt) }; 96 | }, 97 | }); 98 | 99 | pd.connectAccount({ 100 | app: "${selectedApp.data.nameSlug}", 101 | onSuccess: ({ id: accountId }) => { 102 | console.log('🎉 Connection successful!', { accountId }); 103 | }, 104 | });`} 105 | /> 106 |
107 |
108 |

Option 2: Connect Link

109 |
110 | Give your users a link to connect their account when you can't run JavaScript / iFrames. 111 | See the docs 112 | or learn how 113 | Pipedream's MCP server 114 | uses Connect Link URLs in chat interfaces. 115 |
116 | {connectLinkUrl && ( 117 | 123 | {connectLinkUrl}&app={selectedApp.data.nameSlug} 124 | 125 | )} 126 |
127 |
128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /mcp/ai-sdk/index.ts: -------------------------------------------------------------------------------- 1 | import { openai } from "@ai-sdk/openai"; 2 | import { 3 | ModelMessage, 4 | experimental_createMCPClient, 5 | generateText, 6 | stepCountIs, 7 | } from "ai"; 8 | import { 9 | createBaseProgram, 10 | validateAndParseOptions, 11 | createMCPTransport, 12 | logProcessingStart, 13 | logStep, 14 | logToolsLoading, 15 | logAvailableTools, 16 | logAIResponse, 17 | logResponse, 18 | logToolCalls, 19 | logToolResults, 20 | logConversationComplete, 21 | logMaxStepsReached, 22 | logSessionComplete, 23 | logClosingClient, 24 | logClientClosed, 25 | logContinuing, 26 | setupGracefulShutdown, 27 | handleError, 28 | SYSTEM_PROMPT, 29 | ProgramOptions, 30 | } from "../shared/cli.js"; 31 | 32 | const program = createBaseProgram( 33 | "ai-sdk", 34 | "AI SDK CLI tool with MCP integration" 35 | ); 36 | 37 | program.action(async (instruction: string, options: ProgramOptions) => { 38 | const config = validateAndParseOptions(instruction, options); 39 | 40 | let mcpClient: 41 | | Awaited> 42 | | undefined; 43 | 44 | try { 45 | logProcessingStart(config, "AI SDK"); 46 | 47 | const transport = await createMCPTransport(config.options.external_user_id); 48 | 49 | // Initialize the MCP client from AI SDK 50 | mcpClient = await experimental_createMCPClient({ 51 | transport, 52 | }); 53 | 54 | console.log("✅ MCP client initialized"); 55 | 56 | const messages: ModelMessage[] = [ 57 | { 58 | role: "system", 59 | content: SYSTEM_PROMPT, 60 | }, 61 | { 62 | role: "user", 63 | content: [ 64 | { 65 | type: "text", 66 | text: config.instruction, 67 | }, 68 | ], 69 | }, 70 | ]; 71 | 72 | let ended = false; 73 | let steps = 0; 74 | 75 | // Main conversation loop - continues until AI decides to stop or max steps reached 76 | while (!ended && steps < config.maxSteps) { 77 | logStep(steps + 1, config.maxSteps); 78 | 79 | // Reload tools from MCP client before each generation step 80 | // This ensures we have the latest available tools (servers can add/remove tools dynamically) 81 | logToolsLoading(); 82 | const tools = await mcpClient.tools(); 83 | const toolNames = Object.keys(tools).join(", "); 84 | logAvailableTools(toolNames); 85 | 86 | logAIResponse(); 87 | 88 | // Generate response with AI SDK - key configuration: 89 | // - tools: Makes MCP tools available to the model 90 | // - stopWhen: stepCountIs(1) stops after one tool-call step so we can refresh tools each loop 91 | const response = await generateText({ 92 | model: openai(config.options.model as any), 93 | messages, 94 | tools, 95 | stopWhen: stepCountIs(1), // Handle one step at a time so we are able to reload the tools in between steps 96 | }); 97 | 98 | logResponse(response.text); 99 | 100 | // Handle different completion reasons - this determines conversation flow 101 | switch (response.finishReason) { 102 | case "stop": 103 | case "content-filter": 104 | // Model completed its response naturally or was filtered 105 | ended = true; 106 | logConversationComplete(); 107 | break; 108 | 109 | case "error": 110 | // An error occurred during generation 111 | ended = true; 112 | console.error("❌ An error occurred during generation"); 113 | break; 114 | 115 | case "tool-calls": 116 | // Model wants to use tools 117 | // AI SDK automatically executes the tools and provides results 118 | logToolCalls(); 119 | response.toolCalls.forEach((toolCall, index) => { 120 | console.log(` ${index + 1}. ${toolCall.toolName}`); 121 | console.log( 122 | ` Input: ${JSON.stringify(toolCall.input, null, 2)}` 123 | ); 124 | }); 125 | 126 | logToolResults(); 127 | response.toolResults.forEach((result, index) => { 128 | console.log( 129 | ` ${index + 1}. ${JSON.stringify(result.output, null, 2)}` 130 | ); 131 | }); 132 | 133 | // Add the tool calls and results to conversation history 134 | messages.push(...response.response.messages); 135 | break; 136 | 137 | case "length": 138 | console.log("⚠️ Response truncated due to length limit"); 139 | ended = true; 140 | break; 141 | 142 | default: 143 | console.log(`🤔 Unknown finish reason: ${response.finishReason}`); 144 | ended = true; 145 | } 146 | 147 | steps++; 148 | 149 | if (!ended && steps < config.maxSteps) { 150 | logContinuing(); 151 | } 152 | } 153 | 154 | if (steps >= config.maxSteps) { 155 | logMaxStepsReached(config.maxSteps); 156 | } 157 | 158 | logSessionComplete(); 159 | } catch (error) { 160 | handleError(error, "AI SDK"); 161 | } finally { 162 | if (mcpClient) { 163 | logClosingClient(); 164 | await mcpClient.close(); 165 | logClientClosed(); 166 | } 167 | } 168 | }); 169 | 170 | setupGracefulShutdown(); 171 | 172 | program.parse(); 173 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/PipedreamLogo.tsx: -------------------------------------------------------------------------------- 1 | interface PipedreamLogoProps { 2 | className?: string 3 | } 4 | 5 | export function PipedreamLogo({ className }: PipedreamLogoProps) { 6 | return ( 7 | 12 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { DialogProps } from "@radix-ui/react-dialog"; 5 | import { Command as CommandPrimitive } from "cmdk"; 6 | import { IoSearch } from "react-icons/io5"; 7 | 8 | import { cn } from "@/lib/utils"; 9 | import { Dialog, DialogContent } from "@/components/ui/dialog"; 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )); 24 | Command.displayName = CommandPrimitive.displayName; 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
45 | 46 | 54 |
55 | )); 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName; 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )); 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName; 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )); 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName; 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )); 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName; 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName; 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )); 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName; 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ); 142 | }; 143 | CommandShortcut.displayName = "CommandShortcut"; 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | }; 156 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { cn } from "@/lib/utils" 5 | import { IoChevronDownOutline } from "react-icons/io5" 6 | 7 | const NavigationMenu = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, children, ...props }, ref) => ( 11 | 19 | {children} 20 | 21 | 22 | )) 23 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 24 | 25 | const NavigationMenuList = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 39 | 40 | const NavigationMenuItem = NavigationMenuPrimitive.Item 41 | 42 | const navigationMenuTriggerStyle = cva( 43 | "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 44 | ) 45 | 46 | const NavigationMenuTrigger = React.forwardRef< 47 | React.ElementRef, 48 | React.ComponentPropsWithoutRef 49 | >(({ className, children, ...props }, ref) => ( 50 | 55 | {children}{" "} 56 | 61 | )) 62 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 63 | 64 | const NavigationMenuContent = React.forwardRef< 65 | React.ElementRef, 66 | React.ComponentPropsWithoutRef 67 | >(({ className, ...props }, ref) => ( 68 | 76 | )) 77 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 78 | 79 | const NavigationMenuLink = NavigationMenuPrimitive.Link 80 | 81 | const NavigationMenuViewport = React.forwardRef< 82 | React.ElementRef, 83 | React.ComponentPropsWithoutRef 84 | >(({ className, ...props }, ref) => ( 85 |
86 | 94 |
95 | )) 96 | NavigationMenuViewport.displayName = 97 | NavigationMenuPrimitive.Viewport.displayName 98 | 99 | const NavigationMenuIndicator = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 111 |
112 | 113 | )) 114 | NavigationMenuIndicator.displayName = 115 | NavigationMenuPrimitive.Indicator.displayName 116 | 117 | export { 118 | navigationMenuTriggerStyle, 119 | NavigationMenu, 120 | NavigationMenuList, 121 | NavigationMenuItem, 122 | NavigationMenuContent, 123 | NavigationMenuTrigger, 124 | NavigationMenuLink, 125 | NavigationMenuIndicator, 126 | NavigationMenuViewport, 127 | } 128 | -------------------------------------------------------------------------------- /connect-react-demo/scripts/validate-env.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const envPath = path.join(__dirname, '..', '.env'); 7 | const envLocalPath = path.join(__dirname, '..', '.env.local'); 8 | const envExamplePath = path.join(__dirname, '..', '.env.example'); 9 | 10 | function loadEnvFile(filePath) { 11 | if (!fs.existsSync(filePath)) { 12 | return {}; 13 | } 14 | 15 | const envContent = fs.readFileSync(filePath, 'utf8'); 16 | const env = {}; 17 | 18 | envContent.split('\n').forEach(line => { 19 | const trimmedLine = line.trim(); 20 | if (trimmedLine && !trimmedLine.startsWith('#')) { 21 | const [key, ...valueParts] = trimmedLine.split('='); 22 | if (key) { 23 | env[key.trim()] = valueParts.join('=').trim(); 24 | } 25 | } 26 | }); 27 | 28 | return env; 29 | } 30 | 31 | function validateEnvironment() { 32 | console.log('🔍 Validating environment configuration...\n'); 33 | 34 | const errors = []; 35 | const warnings = []; 36 | 37 | // Check if .env or .env.local file exists 38 | const hasEnv = fs.existsSync(envPath); 39 | const hasEnvLocal = fs.existsSync(envLocalPath); 40 | 41 | let env = {}; 42 | 43 | if (!hasEnv && !hasEnvLocal) { 44 | // In deployment environments (like Vercel), use process.env instead of local files 45 | console.log('📁 No local .env files found, using environment variables from deployment platform\n'); 46 | env = process.env; 47 | } else { 48 | // Load env files (env.local takes precedence) 49 | const envBase = hasEnv ? loadEnvFile(envPath) : {}; 50 | const envLocal = hasEnvLocal ? loadEnvFile(envLocalPath) : {}; 51 | env = { ...envBase, ...envLocal }; 52 | 53 | // Log which files are being used 54 | const filesUsed = []; 55 | if (hasEnv) filesUsed.push('.env'); 56 | if (hasEnvLocal) filesUsed.push('.env.local'); 57 | console.log(`📁 Using environment files: ${filesUsed.join(', ')}\n`); 58 | } 59 | 60 | // Required fields 61 | const requiredFields = [ 62 | 'PIPEDREAM_CLIENT_ID', 63 | 'PIPEDREAM_CLIENT_SECRET', 64 | 'PIPEDREAM_PROJECT_ID', 65 | 'PIPEDREAM_PROJECT_ENVIRONMENT', 66 | 'PIPEDREAM_ALLOWED_ORIGINS' 67 | ]; 68 | 69 | // Check required fields 70 | requiredFields.forEach(field => { 71 | if (!env[field] || env[field].includes('<') || env[field].includes('your-')) { 72 | errors.push(`❌ ${field} is not configured properly. Please set a valid value.`); 73 | } 74 | }); 75 | 76 | // Validate PIPEDREAM_PROJECT_ENVIRONMENT 77 | if (env.PIPEDREAM_PROJECT_ENVIRONMENT) { 78 | const validEnvironments = ['development', 'production']; 79 | if (!validEnvironments.includes(env.PIPEDREAM_PROJECT_ENVIRONMENT)) { 80 | errors.push(`❌ PIPEDREAM_PROJECT_ENVIRONMENT must be either 'development' or 'production'. Current value: '${env.PIPEDREAM_PROJECT_ENVIRONMENT}'`); 81 | } 82 | } 83 | 84 | // Validate PIPEDREAM_PROJECT_ID 85 | if (env.PIPEDREAM_PROJECT_ID && !env.PIPEDREAM_PROJECT_ID.includes('<')) { 86 | if (!env.PIPEDREAM_PROJECT_ID.startsWith('proj_')) { 87 | errors.push(`❌ PIPEDREAM_PROJECT_ID must start with 'proj_'. Current value: '${env.PIPEDREAM_PROJECT_ID}'`); 88 | } 89 | } 90 | 91 | // Validate PIPEDREAM_ALLOWED_ORIGINS 92 | if (env.PIPEDREAM_ALLOWED_ORIGINS && !env.PIPEDREAM_ALLOWED_ORIGINS.includes('<')) { 93 | try { 94 | let origins; 95 | let originsValue = env.PIPEDREAM_ALLOWED_ORIGINS; 96 | 97 | // Handle both quoted and unquoted JSON formats 98 | // Remove surrounding quotes if they exist 99 | if (originsValue.startsWith('"') && originsValue.endsWith('"')) { 100 | originsValue = originsValue.slice(1, -1); 101 | } else if (originsValue.startsWith("'") && originsValue.endsWith("'")) { 102 | originsValue = originsValue.slice(1, -1); 103 | } 104 | 105 | origins = JSON.parse(originsValue); 106 | 107 | if (!Array.isArray(origins)) { 108 | errors.push(`❌ PIPEDREAM_ALLOWED_ORIGINS must be a JSON array. Current value: '${env.PIPEDREAM_ALLOWED_ORIGINS}'`); 109 | } else { 110 | // Check if localhost:3000 is included for development 111 | const hasLocalhost = origins.some(origin => origin.includes('localhost:3000')); 112 | if (!hasLocalhost && env.PIPEDREAM_PROJECT_ENVIRONMENT === 'development') { 113 | warnings.push(`⚠️ PIPEDREAM_ALLOWED_ORIGINS doesn't include 'http://localhost:3000'. This may cause issues during local development.`); 114 | } 115 | } 116 | } catch (e) { 117 | errors.push(`❌ PIPEDREAM_ALLOWED_ORIGINS must be valid JSON. Error: ${e.message}`); 118 | } 119 | } 120 | 121 | // Print results 122 | if (errors.length > 0) { 123 | console.error('\n❌ Environment validation failed:\n'); 124 | errors.forEach(error => console.error(` ${error}`)); 125 | 126 | if (warnings.length > 0) { 127 | console.warn('\n⚠️ Warnings:\n'); 128 | warnings.forEach(warning => console.warn(` ${warning}`)); 129 | } 130 | 131 | console.error('\n📝 Please update your .env file with the correct values.\n'); 132 | process.exit(1); 133 | } 134 | 135 | if (warnings.length > 0) { 136 | console.warn('\n⚠️ Warnings:\n'); 137 | warnings.forEach(warning => console.warn(` ${warning}`)); 138 | } 139 | 140 | console.log('✅ Environment configuration is valid!\n'); 141 | console.log('📋 Detected configuration:'); 142 | console.log(` - Environment: ${env.PIPEDREAM_PROJECT_ENVIRONMENT}`); 143 | console.log(` - Project ID: ${env.PIPEDREAM_PROJECT_ID}`); 144 | console.log(` - Allowed Origins: ${env.PIPEDREAM_ALLOWED_ORIGINS}`); 145 | console.log(''); 146 | } 147 | 148 | // Run validation 149 | validateEnvironment(); -------------------------------------------------------------------------------- /connect-react-demo/lib/code-templates.ts: -------------------------------------------------------------------------------- 1 | export interface CodeTemplateData { 2 | externalUserId: string 3 | componentKey?: string 4 | configuredProps: Record 5 | selectedComponentType: "action" | "trigger" 6 | webhookUrl?: string 7 | hideOptionalProps?: boolean 8 | enableDebugging?: boolean 9 | propNames?: string[] 10 | } 11 | 12 | export const generateComponentCode = (data: CodeTemplateData) => { 13 | if (!data.componentKey) { 14 | return `// Select an app and component to see the implementation code 15 | 16 | import { SelectApp, SelectComponent } from "@pipedream/connect-react" 17 | import { useState } from "react" 18 | 19 | function AppSelector() { 20 | const [selectedApp, setSelectedApp] = useState() 21 | const [selectedComponent, setSelectedComponent] = useState() 22 | 23 | return ( 24 |
25 |
26 | 29 | 33 |
34 | 35 | {selectedApp && ( 36 |
37 | 40 | 46 |
47 | )} 48 |
49 | ) 50 | }` 51 | } 52 | 53 | const optionalProps = [ 54 | data.hideOptionalProps && ` hideOptionalProps={true}`, 55 | data.enableDebugging && ` enableDebugging={true}`, 56 | data.propNames?.length && ` propNames={${JSON.stringify(data.propNames)}}`, 57 | ].filter(Boolean).join('\n') 58 | 59 | const webhookUrlLine = data.selectedComponentType === "trigger" 60 | ? `\n webhookUrl: "${data.webhookUrl || 'https://your-app.com/webhook'}",` 61 | : "" 62 | 63 | return `import { ComponentForm, useFrontendClient, useComponent } from "@pipedream/connect-react" 64 | import { useState } from "react" 65 | 66 | function MyComponent() { 67 | const frontendClient = useFrontendClient() 68 | const [configuredProps, setConfiguredProps] = useState(${JSON.stringify(data.configuredProps, null, 2).replace(/\n/g, '\n ')}) 69 | 70 | const { component } = useComponent({ 71 | key: "${data.componentKey}" 72 | }) 73 | 74 | const handleSubmit = async (ctx) => { 75 | const result = await frontendClient.${data.selectedComponentType === "action" ? "actions.run" : "triggers.deploy"}({ 76 | externalUserId: "${data.externalUserId}", 77 | id: "${data.componentKey}", 78 | configuredProps: ctx.configuredProps,${webhookUrlLine} 79 | }) 80 | 81 | // Handle success - show toast, redirect, etc. 82 | } 83 | 84 | return ( 85 |
86 | 93 |
94 | ) 95 | }` 96 | } 97 | 98 | export const generateSetupCode = (externalUserId: string) => `import { FrontendClientProvider } from "@pipedream/connect-react" 99 | import { createFrontendClient } from "@pipedream/sdk/browser" 100 | 101 | export function ClientProvider({ children }) { 102 | const client = createFrontendClient({ 103 | projectEnvironment: process.env.NEXT_PUBLIC_PIPEDREAM_PROJECT_ENVIRONMENT, 104 | tokenCallback: async ({ externalUserId }) => { 105 | // Call your backend to get a Connect token for this user 106 | const response = await fetch('/api/connect/token', { 107 | method: 'POST', 108 | headers: { 'Content-Type': 'application/json' }, 109 | body: JSON.stringify({ externalUserId }), 110 | }) 111 | const { token } = await response.json() 112 | return token 113 | }, 114 | externalUserId: "${externalUserId}", // Your user's unique ID 115 | }) 116 | 117 | return ( 118 | 119 | {children} 120 | 121 | ) 122 | }` 123 | 124 | export const generateApiCode = (externalUserId: string) => `import { NextRequest, NextResponse } from 'next/server' 125 | import { createBackendClient } from '@pipedream/sdk/server' 126 | 127 | const pd = createBackendClient({ 128 | clientId: process.env.PIPEDREAM_CLIENT_ID!, 129 | clientSecret: process.env.PIPEDREAM_CLIENT_SECRET!, 130 | projectEnvironment: process.env.PIPEDREAM_PROJECT_ENVIRONMENT!, 131 | projectId: process.env.PIPEDREAM_PROJECT_ID!, 132 | }) 133 | 134 | export async function POST(request: NextRequest) { 135 | const { externalUserId } = await request.json() 136 | 137 | // Generate a Connect token for this user 138 | const { token } = await pd.createConnectToken({ 139 | external_user_id: externalUserId, 140 | allowed_origins: [ 141 | 'http://localhost:3000', 142 | 'https://your-app.com', 143 | ], 144 | }) 145 | 146 | return NextResponse.json({ token }) 147 | }` 148 | 149 | export const CODE_FILES = [ 150 | { 151 | id: "current", 152 | name: "MyComponent.tsx", 153 | description: "Main React component that renders the integration form and handles user interactions", 154 | icon: "📄" 155 | }, 156 | { 157 | id: "setup", 158 | name: "ClientProvider.tsx", 159 | description: "Provider component that configures the Pipedream SDK and wraps your app", 160 | icon: "⚙️" 161 | }, 162 | { 163 | id: "api", 164 | name: "api/connect/token/route.ts", 165 | description: "Backend API endpoint that securely generates Connect tokens for frontend authentication", 166 | icon: "🔗" 167 | } 168 | ] as const 169 | --------------------------------------------------------------------------------