From 1a5ab3724bfd9ed30d087aafdc7c3b88844c0adb Mon Sep 17 00:00:00 2001 From: Francisco Veiras <74626997+francisco-svg761@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:11:06 -0300 Subject: [PATCH 1/4] feat(connectors): add MCP server support (claude only) --- .env.example | 3 + app/api/connectors/route.ts | 31 + app/api/tasks/route.ts | 30 +- components/app-layout.tsx | 101 +- components/connectors-provider.tsx | 63 + components/connectors/manage-connectors.tsx | 246 + components/home-page-header.tsx | 10 +- components/task-actions.tsx | 10 +- components/ui/accordion.tsx | 53 + components/ui/switch.tsx | 28 + lib/actions/connectors.ts | 141 + lib/crypto.ts | 32 + lib/db/migrations/0005_flashy_nova.sql | 11 + lib/db/migrations/meta/0005_snapshot.json | 191 + lib/db/migrations/meta/_journal.json | 7 + lib/db/schema.ts | 43 + lib/sandbox/agents/claude.ts | 70 +- lib/sandbox/agents/index.ts | 5 +- package.json | 6 +- pnpm-lock.yaml | 6540 +++++++++---------- 20 files changed, 4002 insertions(+), 3619 deletions(-) create mode 100644 app/api/connectors/route.ts create mode 100644 components/connectors-provider.tsx create mode 100644 components/connectors/manage-connectors.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/switch.tsx create mode 100644 lib/actions/connectors.ts create mode 100644 lib/crypto.ts create mode 100644 lib/db/migrations/0005_flashy_nova.sql create mode 100644 lib/db/migrations/meta/0005_snapshot.json diff --git a/.env.example b/.env.example index 658cee0..0b91b9e 100644 --- a/.env.example +++ b/.env.example @@ -14,3 +14,6 @@ VERCEL_TOKEN=your-vercel-token CURSOR_API_KEY=your-cursor-api-key OPENAI_API_KEY=sk-your-openai-key-here NPM_TOKEN=npm_your-npm-token + +# Optional Encryption Key (used to encrypt MCP secrets in the database) +ENCRYPTION_KEY=your-32-byte-hex-key diff --git a/app/api/connectors/route.ts b/app/api/connectors/route.ts new file mode 100644 index 0000000..bedfe5b --- /dev/null +++ b/app/api/connectors/route.ts @@ -0,0 +1,31 @@ +import { NextResponse } from 'next/server' +import { db } from '@/lib/db/client' +import { connectors } from '@/lib/db/schema' +import { decrypt } from '@/lib/crypto' + +export async function GET() { + try { + const allConnectors = await db.select().from(connectors) + + const decryptedConnectors = allConnectors.map((connector) => ({ + ...connector, + oauthClientSecret: connector.oauthClientSecret ? decrypt(connector.oauthClientSecret) : null, + })) + + return NextResponse.json({ + success: true, + data: decryptedConnectors, + }) + } catch (error) { + console.error('Error fetching connectors:', error) + + return NextResponse.json( + { + success: false, + error: 'Failed to fetch connectors', + data: [], + }, + { status: 500 }, + ) + } +} diff --git a/app/api/tasks/route.ts b/app/api/tasks/route.ts index 8f157d0..39d28ed 100644 --- a/app/api/tasks/route.ts +++ b/app/api/tasks/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse, after } from 'next/server' import { Sandbox } from '@vercel/sandbox' import { db } from '@/lib/db/client' -import { tasks, insertTaskSchema } from '@/lib/db/schema' +import { tasks, insertTaskSchema, connectors } from '@/lib/db/schema' import { generateId } from '@/lib/utils/id' import { createSandbox } from '@/lib/sandbox/creation' import { executeAgentInSandbox, AgentType } from '@/lib/sandbox/agents' @@ -10,6 +10,7 @@ import { eq, desc, or } from 'drizzle-orm' import { createInfoLog } from '@/lib/utils/logging' import { createTaskLogger } from '@/lib/utils/task-logger' import { generateBranchName, createFallbackBranchName } from '@/lib/utils/branch-name-generator' +import { decrypt } from '@/lib/crypto' export async function GET() { try { @@ -298,8 +299,33 @@ async function processTask( throw new Error('Sandbox is not available for agent execution') } + type Connector = typeof connectors.$inferSelect + + let mcpServers: Connector[] = [] + + try { + const allConnectors = await db.select().from(connectors) + mcpServers = allConnectors + .filter((connector: Connector) => connector.status === 'connected') + .map((connector: Connector) => { + return { + ...connector, + oauthClientSecret: connector.oauthClientSecret ? decrypt(connector.oauthClientSecret) : null, + } + }) + + if (mcpServers.length > 0) { + await logger.info( + `Found ${mcpServers.length} connected MCP servers: ${mcpServers.map((s) => s.name).join(', ')}`, + ) + } + } catch (mcpError) { + console.error('Failed to fetch MCP servers:', mcpError) + await logger.info('Warning: Could not fetch MCP servers, continuing without them') + } + const agentResult = await Promise.race([ - executeAgentInSandbox(sandbox, prompt, selectedAgent as AgentType, logger, selectedModel), + executeAgentInSandbox(sandbox, prompt, selectedAgent as AgentType, logger, selectedModel, mcpServers), agentTimeoutPromise, ]) diff --git a/components/app-layout.tsx b/components/app-layout.tsx index 3643bba..223a1fd 100644 --- a/components/app-layout.tsx +++ b/components/app-layout.tsx @@ -2,13 +2,14 @@ import { useState, useEffect, createContext, useContext, useCallback } from 'react' import { TaskSidebar } from '@/components/task-sidebar' -import { Task } from '@/lib/db/schema' +import { Task, Connector } from '@/lib/db/schema' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Plus } from 'lucide-react' import Link from 'next/link' import { getSidebarWidth, setSidebarWidth, getSidebarOpen, setSidebarOpen } from '@/lib/utils/cookies' import { nanoid } from 'nanoid' +import { ConnectorsProvider } from '@/components/connectors-provider' interface AppLayoutProps { children: React.ReactNode @@ -258,72 +259,74 @@ export function AppLayout({ children, initialSidebarWidth, initialSidebarOpen }: return ( -
- {/* Backdrop - Mobile Only */} - {isSidebarOpen &&
} - - {/* Sidebar */} +
+ {/* Backdrop - Mobile Only */} + {isSidebarOpen &&
} + + {/* Sidebar */} +
-
- {isLoading ? ( - - ) : ( - - )} +
+ {isLoading ? ( + + ) : ( + + )} +
-
- {/* Resize Handle - Desktop Only, when sidebar is open */} -