improvement(mcp): ditch custom mcp client in favor of mcp sdk (#1780)

This commit is contained in:
Waleed
2025-10-31 11:21:00 -07:00
committed by GitHub
parent 6cd82f07ed
commit 70ff5394a4
13 changed files with 154 additions and 1250 deletions

View File

@@ -17,7 +17,7 @@ export const dynamic = 'force-dynamic'
* Check if transport type requires a URL
*/
function isUrlBasedTransport(transport: McpTransport): boolean {
return transport === 'http' || transport === 'sse' || transport === 'streamable-http'
return transport === 'streamable-http'
}
/**

View File

@@ -13,9 +13,10 @@ export const dynamic = 'force-dynamic'
/**
* Check if transport type requires a URL
* All modern MCP connections use Streamable HTTP which requires a URL
*/
function isUrlBasedTransport(transport: McpTransport): boolean {
return transport === 'http' || transport === 'sse' || transport === 'streamable-http'
return transport === 'streamable-http'
}
/**
@@ -151,16 +152,21 @@ export const POST = withMcpAuth('write')(
client = new McpClient(testConfig, testSecurityPolicy)
await client.connect()
result.success = true
result.negotiatedVersion = client.getNegotiatedVersion()
try {
const tools = await client.listTools()
result.toolCount = tools.length
result.success = true
} catch (toolError) {
logger.warn(`[${requestId}] Could not list tools from test server:`, toolError)
logger.warn(`[${requestId}] Connection established but could not list tools:`, toolError)
result.success = false
const errorMessage = toolError instanceof Error ? toolError.message : 'Unknown error'
result.error = `Connection established but could not list tools: ${errorMessage}`
result.warnings = result.warnings || []
result.warnings.push('Could not list tools from server')
result.warnings.push(
'Server connected but tool listing failed - connection may be incomplete'
)
}
const clientVersionInfo = McpClient.getVersionInfo()

View File

@@ -1,581 +0,0 @@
'use client'
import { useCallback, useRef, useState } from 'react'
import { X } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { createLogger } from '@/lib/logs/console/logger'
import type { McpTransport } from '@/lib/mcp/types'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { useMcpServerTest } from '@/hooks/use-mcp-server-test'
import { useMcpServersStore } from '@/stores/mcp-servers/store'
const logger = createLogger('McpServerModal')
interface McpServerModalProps {
open: boolean
onOpenChange: (open: boolean) => void
onServerCreated?: () => void
blockId: string
}
interface McpServerFormData {
name: string
transport: McpTransport
url?: string
headers?: Record<string, string>
}
export function McpServerModal({
open,
onOpenChange,
onServerCreated,
blockId,
}: McpServerModalProps) {
const params = useParams()
const workspaceId = params.workspaceId as string
const [formData, setFormData] = useState<McpServerFormData>({
name: '',
transport: 'streamable-http',
url: '',
headers: { '': '' },
})
const { createServer, isLoading, error: storeError, clearError } = useMcpServersStore()
const [localError, setLocalError] = useState<string | null>(null)
// MCP server testing
const { testResult, isTestingConnection, testConnection, clearTestResult } = useMcpServerTest()
// Environment variable dropdown state
const [showEnvVars, setShowEnvVars] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
const [cursorPosition, setCursorPosition] = useState(0)
const [activeInputField, setActiveInputField] = useState<
'url' | 'header-key' | 'header-value' | null
>(null)
const [activeHeaderIndex, setActiveHeaderIndex] = useState<number | null>(null)
const urlInputRef = useRef<HTMLInputElement>(null)
const [urlScrollLeft, setUrlScrollLeft] = useState(0)
const [headerScrollLeft, setHeaderScrollLeft] = useState<Record<string, number>>({})
const error = localError || storeError
const resetForm = () => {
setFormData({
name: '',
transport: 'streamable-http',
url: '',
headers: { '': '' },
})
setLocalError(null)
clearError()
setShowEnvVars(false)
setActiveInputField(null)
setActiveHeaderIndex(null)
clearTestResult()
}
// Handle environment variable selection
const handleEnvVarSelect = useCallback(
(newValue: string) => {
if (activeInputField === 'url') {
setFormData((prev) => ({ ...prev, url: newValue }))
} else if (activeInputField === 'header-key' && activeHeaderIndex !== null) {
const headerEntries = Object.entries(formData.headers || {})
const [oldKey, value] = headerEntries[activeHeaderIndex]
const newHeaders = { ...formData.headers }
delete newHeaders[oldKey]
newHeaders[newValue.replace(/[{}]/g, '')] = value
setFormData((prev) => ({ ...prev, headers: newHeaders }))
} else if (activeInputField === 'header-value' && activeHeaderIndex !== null) {
const headerEntries = Object.entries(formData.headers || {})
const [key] = headerEntries[activeHeaderIndex]
setFormData((prev) => ({
...prev,
headers: { ...prev.headers, [key]: newValue },
}))
}
setShowEnvVars(false)
setActiveInputField(null)
setActiveHeaderIndex(null)
},
[activeInputField, activeHeaderIndex, formData.headers]
)
// Handle input change with env var detection
const handleInputChange = useCallback(
(field: 'url' | 'header-key' | 'header-value', value: string, headerIndex?: number) => {
const input = document.activeElement as HTMLInputElement
const pos = input?.selectionStart || 0
setCursorPosition(pos)
// Clear test result when any field changes
if (testResult) {
clearTestResult()
}
// Check if we should show the environment variables dropdown
const envVarTrigger = checkEnvVarTrigger(value, pos)
setShowEnvVars(envVarTrigger.show)
setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '')
if (envVarTrigger.show) {
setActiveInputField(field)
setActiveHeaderIndex(headerIndex ?? null)
} else {
setActiveInputField(null)
setActiveHeaderIndex(null)
}
// Update form data
if (field === 'url') {
setFormData((prev) => ({ ...prev, url: value }))
} else if (field === 'header-key' && headerIndex !== undefined) {
const headerEntries = Object.entries(formData.headers || {})
const [oldKey, headerValue] = headerEntries[headerIndex]
const newHeaders = { ...formData.headers }
delete newHeaders[oldKey]
newHeaders[value] = headerValue
// Add a new empty header row if this is the last row and both key and value have content
const isLastRow = headerIndex === headerEntries.length - 1
const hasContent = value.trim() !== '' && headerValue.trim() !== ''
if (isLastRow && hasContent) {
newHeaders[''] = ''
}
setFormData((prev) => ({ ...prev, headers: newHeaders }))
} else if (field === 'header-value' && headerIndex !== undefined) {
const headerEntries = Object.entries(formData.headers || {})
const [key] = headerEntries[headerIndex]
const newHeaders = { ...formData.headers, [key]: value }
// Add a new empty header row if this is the last row and both key and value have content
const isLastRow = headerIndex === headerEntries.length - 1
const hasContent = key.trim() !== '' && value.trim() !== ''
if (isLastRow && hasContent) {
newHeaders[''] = ''
}
setFormData((prev) => ({ ...prev, headers: newHeaders }))
}
},
[formData.headers, testResult, clearTestResult]
)
const handleTestConnection = useCallback(async () => {
if (!formData.name.trim() || !formData.url?.trim()) return
await testConnection({
name: formData.name,
transport: formData.transport,
url: formData.url,
headers: formData.headers,
timeout: 30000,
workspaceId,
})
}, [formData, testConnection, workspaceId])
const handleSubmit = useCallback(async () => {
if (!formData.name.trim()) {
setLocalError('Server name is required')
return
}
if (!formData.url?.trim()) {
setLocalError('Server URL is required for HTTP/SSE transport')
return
}
setLocalError(null)
clearError()
try {
// If no test has been done, test first
if (!testResult) {
const result = await testConnection({
name: formData.name,
transport: formData.transport,
url: formData.url,
headers: formData.headers,
timeout: 30000,
workspaceId,
})
// If test fails, don't proceed
if (!result.success) {
return
}
}
// If we have a failed test result, don't proceed
if (testResult && !testResult.success) {
return
}
// Filter out empty headers
const cleanHeaders = Object.fromEntries(
Object.entries(formData.headers || {}).filter(
([key, value]) => key.trim() !== '' && value.trim() !== ''
)
)
await createServer(workspaceId, {
name: formData.name.trim(),
transport: formData.transport,
url: formData.url,
timeout: 30000,
headers: cleanHeaders,
enabled: true,
})
logger.info(`Added MCP server: ${formData.name}`)
// Close modal and reset form immediately after successful creation
resetForm()
onOpenChange(false)
onServerCreated?.()
} catch (error) {
logger.error('Failed to add MCP server:', error)
setLocalError(error instanceof Error ? error.message : 'Failed to add MCP server')
}
}, [
formData,
testResult,
testConnection,
onOpenChange,
onServerCreated,
createServer,
clearError,
workspaceId,
])
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className='sm:max-w-[600px]'>
<DialogHeader>
<DialogTitle>Add MCP Server</DialogTitle>
<DialogDescription>
Configure a new Model Context Protocol server to extend your workflow capabilities.
</DialogDescription>
</DialogHeader>
<div className='space-y-4 py-4'>
<div className='grid grid-cols-2 gap-4'>
<div>
<Label htmlFor='server-name'>Server Name</Label>
<Input
id='server-name'
placeholder='e.g., My MCP Server'
value={formData.name}
onChange={(e) => {
if (testResult) clearTestResult()
setFormData((prev) => ({ ...prev, name: e.target.value }))
}}
className='h-9'
/>
</div>
<div>
<Label htmlFor='transport'>Transport Type</Label>
<Select
value={formData.transport}
onValueChange={(value: 'http' | 'sse' | 'streamable-http') => {
if (testResult) clearTestResult()
setFormData((prev) => ({
...prev,
transport: value,
}))
}}
>
<SelectTrigger className='h-9'>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value='streamable-http'>Streamable HTTP</SelectItem>
<SelectItem value='http'>HTTP</SelectItem>
<SelectItem value='sse'>Server-Sent Events</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className='relative'>
<Label htmlFor='server-url'>Server URL</Label>
<div className='relative'>
<Input
ref={urlInputRef}
id='server-url'
placeholder='https://mcp.server.dev/{{YOUR_API_KEY}}/sse'
value={formData.url}
onChange={(e) => handleInputChange('url', e.target.value)}
onScroll={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setUrlScrollLeft(scrollLeft)
}}
onInput={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setUrlScrollLeft(scrollLeft)
}}
className='h-9 text-transparent caret-foreground placeholder:text-muted-foreground/50'
/>
{/* Overlay for styled text display */}
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 text-sm'>
<div
className='whitespace-nowrap'
style={{ transform: `translateX(-${urlScrollLeft}px)` }}
>
{formatDisplayText(formData.url || '', {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})}
</div>
</div>
</div>
{/* Environment Variables Dropdown */}
{showEnvVars && activeInputField === 'url' && (
<EnvVarDropdown
visible={showEnvVars}
onSelect={handleEnvVarSelect}
searchTerm={searchTerm}
inputValue={formData.url || ''}
cursorPosition={cursorPosition}
workspaceId={workspaceId}
onClose={() => {
setShowEnvVars(false)
setActiveInputField(null)
}}
className='w-full'
maxHeight='250px'
/>
)}
</div>
<div>
<Label>Headers (Optional)</Label>
<div className='space-y-2'>
{Object.entries(formData.headers || {}).map(([key, value], index) => (
<div key={index} className='relative flex gap-2'>
{/* Header Name Input */}
<div className='relative flex-1'>
<Input
placeholder='Name'
value={key}
onChange={(e) => handleInputChange('header-key', e.target.value, index)}
onScroll={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setHeaderScrollLeft((prev) => ({ ...prev, [`key-${index}`]: scrollLeft }))
}}
onInput={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setHeaderScrollLeft((prev) => ({ ...prev, [`key-${index}`]: scrollLeft }))
}}
className='h-9 text-transparent caret-foreground placeholder:text-muted-foreground/50'
/>
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 text-sm'>
<div
className='whitespace-nowrap'
style={{
transform: `translateX(-${headerScrollLeft[`key-${index}`] || 0}px)`,
}}
>
{formatDisplayText(key || '', {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})}
</div>
</div>
</div>
{/* Header Value Input */}
<div className='relative flex-1'>
<Input
placeholder='Value'
value={value}
onChange={(e) => handleInputChange('header-value', e.target.value, index)}
onScroll={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setHeaderScrollLeft((prev) => ({ ...prev, [`value-${index}`]: scrollLeft }))
}}
onInput={(e) => {
const scrollLeft = e.currentTarget.scrollLeft
setHeaderScrollLeft((prev) => ({ ...prev, [`value-${index}`]: scrollLeft }))
}}
className='h-9 text-transparent caret-foreground placeholder:text-muted-foreground/50'
/>
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 text-sm'>
<div
className='whitespace-nowrap'
style={{
transform: `translateX(-${headerScrollLeft[`value-${index}`] || 0}px)`,
}}
>
{formatDisplayText(value || '', {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})}
</div>
</div>
</div>
<Button
type='button'
variant='ghost'
onClick={() => {
const headerEntries = Object.entries(formData.headers || {})
if (headerEntries.length === 1) {
// If this is the only header, just clear it instead of deleting
setFormData((prev) => ({ ...prev, headers: { '': '' } }))
} else {
// Delete this header
const newHeaders = { ...formData.headers }
delete newHeaders[key]
setFormData((prev) => ({ ...prev, headers: newHeaders }))
}
}}
className='h-9 w-9 p-0 text-muted-foreground hover:text-foreground'
>
<X className='h-3 w-3' />
</Button>
{/* Environment Variables Dropdown for Header Key */}
{showEnvVars &&
activeInputField === 'header-key' &&
activeHeaderIndex === index && (
<EnvVarDropdown
visible={showEnvVars}
onSelect={handleEnvVarSelect}
searchTerm={searchTerm}
inputValue={key}
cursorPosition={cursorPosition}
workspaceId={workspaceId}
onClose={() => {
setShowEnvVars(false)
setActiveInputField(null)
setActiveHeaderIndex(null)
}}
className='w-full'
maxHeight='150px'
style={{
position: 'absolute',
top: '100%',
left: 0,
zIndex: 9999,
}}
/>
)}
{/* Environment Variables Dropdown for Header Value */}
{showEnvVars &&
activeInputField === 'header-value' &&
activeHeaderIndex === index && (
<EnvVarDropdown
visible={showEnvVars}
onSelect={handleEnvVarSelect}
searchTerm={searchTerm}
inputValue={value}
cursorPosition={cursorPosition}
workspaceId={workspaceId}
onClose={() => {
setShowEnvVars(false)
setActiveInputField(null)
setActiveHeaderIndex(null)
}}
className='w-full'
maxHeight='250px'
style={{
position: 'absolute',
top: '100%',
right: 0,
zIndex: 9999,
}}
/>
)}
</div>
))}
</div>
</div>
{error && (
<div className='rounded-md bg-destructive/10 px-3 py-2 text-destructive text-sm'>
{error}
</div>
)}
{/* Test Connection and Actions */}
<div className='border-t pt-4'>
<div className='flex items-center justify-between'>
<div className='space-y-2'>
<div className='flex items-center gap-2'>
<Button
type='button'
variant='ghost'
size='sm'
onClick={handleTestConnection}
disabled={isTestingConnection || !formData.name.trim() || !formData.url?.trim()}
className='text-muted-foreground hover:text-foreground'
>
{isTestingConnection ? 'Testing...' : 'Test Connection'}
</Button>
{testResult?.success && (
<span className='text-green-600 text-xs'> Connected</span>
)}
</div>
{testResult && !testResult.success && (
<div className='rounded border border-red-200 bg-red-50 px-2 py-1.5 text-red-600 text-xs dark:border-red-800 dark:bg-red-950/20'>
<div className='font-medium'>Connection failed</div>
<div className='text-red-500 dark:text-red-400'>
{testResult.error || testResult.message}
</div>
</div>
)}
</div>
<div className='flex gap-2'>
<Button
variant='ghost'
size='sm'
onClick={() => {
resetForm()
onOpenChange(false)
}}
disabled={isLoading}
>
Cancel
</Button>
<Button
size='sm'
onClick={handleSubmit}
disabled={isLoading || !formData.name.trim() || !formData.url?.trim()}
>
{isLoading ? 'Adding...' : 'Add Server'}
</Button>
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -30,7 +30,6 @@ const getProviderIcon = (providerName: OAuthProvider) => {
if (!baseProviderConfig) {
return <ExternalLink className='h-4 w-4' />
}
// Always use the base provider icon for a more consistent UI
return baseProviderConfig.icon({ className: 'h-4 w-4' })
}
@@ -42,7 +41,6 @@ const getProviderName = (providerName: OAuthProvider) => {
return baseProviderConfig.name
}
// Fallback: capitalize the provider name
return providerName
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
@@ -75,7 +73,6 @@ export function ToolCredentialSelector({
const [selectedId, setSelectedId] = useState('')
const { activeWorkflowId } = useWorkflowRegistry()
// Update selected ID when value changes
useEffect(() => {
setSelectedId(value)
}, [value])
@@ -88,7 +85,6 @@ export function ToolCredentialSelector({
const data = await response.json()
setCredentials(data.credentials || [])
// If persisted selection is not among viewer's credentials, attempt to fetch its metadata
if (
value &&
!(data.credentials || []).some((cred: Credential) => cred.id === value) &&
@@ -127,7 +123,6 @@ export function ToolCredentialSelector({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Listen for visibility changes to update credentials when user returns from settings
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
@@ -150,15 +145,12 @@ export function ToolCredentialSelector({
const handleOAuthClose = () => {
setShowOAuthModal(false)
// Refetch credentials to include any new ones
fetchCredentials()
}
// Handle popover open to fetch fresh credentials
const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen)
if (isOpen) {
// Fetch fresh credentials when opening the dropdown
fetchCredentials()
}
}

View File

@@ -35,7 +35,6 @@ import {
type CustomTool,
CustomToolModal,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal'
import { McpServerModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/mcp-server-modal/mcp-server-modal'
import { McpToolsList } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/mcp-tools-list'
import { ToolCommand } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/tool-command/tool-command'
import { ToolCredentialSelector } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/tool-credential-selector'
@@ -430,7 +429,6 @@ export function ToolInput({
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
const [open, setOpen] = useState(false)
const [customToolModalOpen, setCustomToolModalOpen] = useState(false)
const [mcpServerModalOpen, setMcpServerModalOpen] = useState(false)
const [editingToolIndex, setEditingToolIndex] = useState<number | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [draggedIndex, setDraggedIndex] = useState<number | null>(null)
@@ -1274,8 +1272,10 @@ export function ToolInput({
value='Add MCP Server'
onSelect={() => {
if (!isPreview) {
setMcpServerModalOpen(true)
setOpen(false)
window.dispatchEvent(
new CustomEvent('open-settings', { detail: { tab: 'mcp' } })
)
}
}}
className='mb-1 flex cursor-pointer items-center gap-2'
@@ -1839,7 +1839,9 @@ export function ToolInput({
value='Add MCP Server'
onSelect={() => {
setOpen(false)
setMcpServerModalOpen(true)
window.dispatchEvent(
new CustomEvent('open-settings', { detail: { tab: 'mcp' } })
)
}}
className='mb-1 flex cursor-pointer items-center gap-2'
>
@@ -1976,17 +1978,6 @@ export function ToolInput({
: undefined
}
/>
{/* MCP Server Modal */}
<McpServerModal
open={mcpServerModalOpen}
onOpenChange={setMcpServerModalOpen}
onServerCreated={() => {
// Refresh MCP tools when a new server is created
refreshTools(true)
}}
blockId={blockId}
/>
</div>
)
}

View File

@@ -3,19 +3,7 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { AlertCircle, Plus, Search, X } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
Alert,
AlertDescription,
Button,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Skeleton,
} from '@/components/ui'
import { Alert, AlertDescription, Button, Input, Label, Skeleton } from '@/components/ui'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { createLogger } from '@/lib/logs/console/logger'
@@ -349,30 +337,6 @@ export function MCP() {
</div>
</div>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<Label className='font-normal'>Transport</Label>
</div>
<div className='w-[380px]'>
<Select
value={formData.transport}
onValueChange={(value: 'http' | 'sse' | 'streamable-http') => {
if (testResult) clearTestResult()
setFormData((prev) => ({ ...prev, transport: value }))
}}
>
<SelectTrigger className='h-9'>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value='streamable-http'>Streamable HTTP</SelectItem>
<SelectItem value='http'>HTTP</SelectItem>
<SelectItem value='sse'>Server-Sent Events</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<Label className='font-normal'>Server URL</Label>
@@ -728,30 +692,6 @@ export function MCP() {
</div>
</div>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<Label className='font-normal'>Transport</Label>
</div>
<div className='w-[380px]'>
<Select
value={formData.transport}
onValueChange={(value: 'http' | 'sse' | 'streamable-http') => {
if (testResult) clearTestResult()
setFormData((prev) => ({ ...prev, transport: value }))
}}
>
<SelectTrigger className='h-9'>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value='streamable-http'>Streamable HTTP</SelectItem>
<SelectItem value='http'>HTTP</SelectItem>
<SelectItem value='sse'>Server-Sent Events</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<Label className='font-normal'>Server URL</Label>

View File

@@ -10,7 +10,7 @@ const logger = createLogger('useMcpServerTest')
* Check if transport type requires a URL
*/
function isUrlBasedTransport(transport: McpTransport): boolean {
return transport === 'http' || transport === 'sse' || transport === 'streamable-http'
return transport === 'streamable-http'
}
export interface McpServerTestConfig {

View File

@@ -1,28 +1,25 @@
/**
* MCP (Model Context Protocol) JSON-RPC 2.0 Client
* MCP (Model Context Protocol) Client
*
* Implements the client side of MCP protocol with support for:
* - Streamable HTTP transport (MCP 2025-03-26)
* - Connection lifecycle management
* - Streamable HTTP transport (MCP 2025-06-18)
* - Tool execution and discovery
* - Session management with Mcp-Session-Id header
* - Session management and protocol version negotiation
* - Custom security/consent layer
*/
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import type { ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js'
import { createLogger } from '@/lib/logs/console/logger'
import {
type JsonRpcRequest,
type JsonRpcResponse,
type McpCapabilities,
McpConnectionError,
type McpConnectionStatus,
type McpConsentRequest,
type McpConsentResponse,
McpError,
type McpInitializeParams,
type McpInitializeResult,
type McpSecurityPolicy,
type McpServerConfig,
McpTimeoutError,
type McpTool,
type McpToolCall,
type McpToolResult,
@@ -32,23 +29,13 @@ import {
const logger = createLogger('McpClient')
export class McpClient {
private client: Client
private transport: StreamableHTTPClientTransport
private config: McpServerConfig
private connectionStatus: McpConnectionStatus
private requestId = 0
private pendingRequests = new Map<
string | number,
{
resolve: (value: JsonRpcResponse) => void
reject: (error: Error) => void
timeout: NodeJS.Timeout
}
>()
private serverCapabilities?: McpCapabilities
private mcpSessionId?: string
private negotiatedVersion?: string
private securityPolicy: McpSecurityPolicy
private isConnected = false
// Supported protocol versions
private static readonly SUPPORTED_VERSIONS = [
'2025-06-18', // Latest stable with elicitation and OAuth 2.1
'2025-03-26', // Streamable HTTP support
@@ -58,12 +45,36 @@ export class McpClient {
constructor(config: McpServerConfig, securityPolicy?: McpSecurityPolicy) {
this.config = config
this.connectionStatus = { connected: false }
this.securityPolicy = securityPolicy ?? {
requireConsent: true,
auditLevel: 'basic',
maxToolExecutionsPerHour: 1000,
}
if (!this.config.url) {
throw new McpError('URL required for Streamable HTTP transport')
}
this.transport = new StreamableHTTPClientTransport(new URL(this.config.url), {
requestInit: {
headers: this.config.headers,
},
})
this.client = new Client(
{
name: 'sim-platform',
version: '1.0.0',
},
{
capabilities: {
tools: {},
// Resources and prompts can be added later
// resources: {},
// prompts: {},
},
}
)
}
/**
@@ -73,28 +84,20 @@ export class McpClient {
logger.info(`Connecting to MCP server: ${this.config.name} (${this.config.transport})`)
try {
switch (this.config.transport) {
case 'http':
await this.connectStreamableHttp()
break
case 'sse':
await this.connectStreamableHttp()
break
case 'streamable-http':
await this.connectStreamableHttp()
break
default:
throw new McpError(`Unsupported transport: ${this.config.transport}`)
}
await this.client.connect(this.transport)
await this.initialize()
this.isConnected = true
this.connectionStatus.connected = true
this.connectionStatus.lastConnected = new Date()
logger.info(`Successfully connected to MCP server: ${this.config.name}`)
const serverVersion = this.client.getServerVersion()
logger.info(`Successfully connected to MCP server: ${this.config.name}`, {
protocolVersion: serverVersion,
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
this.connectionStatus.lastError = errorMessage
this.isConnected = false
logger.error(`Failed to connect to MCP server ${this.config.name}:`, error)
throw new McpConnectionError(errorMessage, this.config.id)
}
@@ -106,12 +109,13 @@ export class McpClient {
async disconnect(): Promise<void> {
logger.info(`Disconnecting from MCP server: ${this.config.name}`)
for (const [, pending] of this.pendingRequests) {
clearTimeout(pending.timeout)
pending.reject(new McpError('Connection closed'))
try {
await this.client.close()
} catch (error) {
logger.warn(`Error during disconnect from ${this.config.name}:`, error)
}
this.pendingRequests.clear()
this.isConnected = false
this.connectionStatus.connected = false
logger.info(`Disconnected from MCP server: ${this.config.name}`)
}
@@ -127,19 +131,19 @@ export class McpClient {
* List all available tools from the server
*/
async listTools(): Promise<McpTool[]> {
if (!this.connectionStatus.connected) {
if (!this.isConnected) {
throw new McpConnectionError('Not connected to server', this.config.id)
}
try {
const response = await this.sendRequest('tools/list', {})
const result: ListToolsResult = await this.client.listTools()
if (!response.tools || !Array.isArray(response.tools)) {
logger.warn(`Invalid tools response from server ${this.config.name}:`, response)
if (!result.tools || !Array.isArray(result.tools)) {
logger.warn(`Invalid tools response from server ${this.config.name}:`, result)
return []
}
return response.tools.map((tool: any) => ({
return result.tools.map((tool: Tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
@@ -156,11 +160,10 @@ export class McpClient {
* Execute a tool on the MCP server
*/
async callTool(toolCall: McpToolCall): Promise<McpToolResult> {
if (!this.connectionStatus.connected) {
if (!this.isConnected) {
throw new McpConnectionError('Not connected to server', this.config.id)
}
// Request consent for tool execution
const consentRequest: McpConsentRequest = {
type: 'tool_execution',
context: {
@@ -171,7 +174,7 @@ export class McpClient {
dataAccess: Object.keys(toolCall.arguments || {}),
sideEffects: ['tool_execution'],
},
expires: Date.now() + 5 * 60 * 1000, // 5 minute consent window
expires: Date.now() + 5 * 60 * 1000,
}
const consentResponse = await this.requestConsent(consentRequest)
@@ -184,16 +187,15 @@ export class McpClient {
try {
logger.info(`Calling tool ${toolCall.name} on server ${this.config.name}`, {
consentAuditId: consentResponse.auditId,
protocolVersion: this.negotiatedVersion,
protocolVersion: this.getNegotiatedVersion(),
})
const response = await this.sendRequest('tools/call', {
const sdkResult = await this.client.callTool({
name: toolCall.name,
arguments: toolCall.arguments,
})
// The response is the JSON-RPC 'result' field
return response as McpToolResult
return sdkResult as McpToolResult
} catch (error) {
logger.error(`Failed to call tool ${toolCall.name} on server ${this.config.name}:`, error)
throw error
@@ -201,337 +203,31 @@ export class McpClient {
}
/**
* Send a JSON-RPC request to the server
* Ping the server to check if it's still alive and responsive
* Per MCP spec: servers should respond to ping requests
*/
private async sendRequest(method: string, params: any): Promise<any> {
const id = ++this.requestId
const request: JsonRpcRequest = {
jsonrpc: '2.0',
id,
method,
params,
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingRequests.delete(id)
reject(new McpTimeoutError(this.config.id, this.config.timeout || 30000))
}, this.config.timeout || 30000)
this.pendingRequests.set(id, { resolve, reject, timeout })
this.sendHttpRequest(request).catch(reject)
})
}
/**
* Initialize connection with capability and version negotiation
*/
private async initialize(): Promise<void> {
// Start with latest supported version for negotiation
const preferredVersion = McpClient.SUPPORTED_VERSIONS[0]
const initParams: McpInitializeParams = {
protocolVersion: preferredVersion,
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true, listChanged: true },
prompts: { listChanged: true },
logging: { level: 'info' },
},
clientInfo: {
name: 'sim-platform',
version: '1.0.0',
},
async ping(): Promise<{ _meta?: Record<string, any> }> {
if (!this.isConnected) {
throw new McpConnectionError('Not connected to server', this.config.id)
}
try {
const result: McpInitializeResult = await this.sendRequest('initialize', initParams)
// Handle version negotiation
if (result.protocolVersion !== preferredVersion) {
// Server proposed a different version - check if we support it
if (!McpClient.SUPPORTED_VERSIONS.includes(result.protocolVersion)) {
// Client SHOULD disconnect if it cannot support proposed version
throw new McpError(
`Version negotiation failed: Server proposed unsupported version '${result.protocolVersion}'. ` +
`This client supports versions: ${McpClient.SUPPORTED_VERSIONS.join(', ')}. ` +
`To use this server, you may need to update your client or find a compatible version of the server.`
)
}
logger.info(
`Version negotiation: Server proposed version '${result.protocolVersion}' ` +
`instead of requested '${preferredVersion}'. Using server version.`
)
}
this.negotiatedVersion = result.protocolVersion
this.serverCapabilities = result.capabilities
logger.info(`MCP initialization successful with protocol version '${this.negotiatedVersion}'`)
logger.info(`[${this.config.name}] Sending ping to server`)
const response = await this.client.ping()
logger.info(`[${this.config.name}] Ping successful`)
return response
} catch (error) {
// Enhanced error handling
if (error instanceof McpError) {
throw error // Re-throw MCP errors as-is
}
// Handle network errors
if (error instanceof Error) {
if (error.message.includes('fetch') || error.message.includes('network')) {
throw new McpError(
`Failed to connect to MCP server '${this.config.name}': ${error.message}. ` +
`Please check the server URL and ensure the server is running.`
)
}
if (error.message.includes('timeout')) {
throw new McpError(
`Connection timeout to MCP server '${this.config.name}'. ` +
`The server may be slow to respond or unreachable.`
)
}
// Generic error
throw new McpError(
`Connection to MCP server '${this.config.name}' failed: ${error.message}. ` +
`Please verify the server configuration and try again.`
)
}
throw new McpError(`Unexpected error during MCP initialization: ${String(error)}`)
}
await this.sendNotification('notifications/initialized', {})
}
/**
* Send a notification
*/
private async sendNotification(method: string, params: any): Promise<void> {
const notification = {
jsonrpc: '2.0' as const,
method,
params,
}
await this.sendHttpRequest(notification)
}
/**
* Connect using Streamable HTTP transport
*/
private async connectStreamableHttp(): Promise<void> {
if (!this.config.url) {
throw new McpError('URL required for Streamable HTTP transport')
}
logger.info(`Using Streamable HTTP transport for ${this.config.name}`)
}
/**
* Send HTTP request with automatic retry
*/
private async sendHttpRequest(request: JsonRpcRequest | any): Promise<void> {
if (!this.config.url) {
throw new McpError('URL required for HTTP transport')
}
const urlsToTry = [this.config.url]
if (!this.config.url.endsWith('/')) {
urlsToTry.push(`${this.config.url}/`)
} else {
urlsToTry.push(this.config.url.slice(0, -1))
}
let lastError: Error | null = null
const originalUrl = this.config.url
for (const [index, url] of urlsToTry.entries()) {
try {
await this.attemptHttpRequest(request, url, index === 0)
if (index > 0) {
logger.info(
`[${this.config.name}] Successfully used alternative URL format: ${url} (original: ${originalUrl})`
)
}
return
} catch (error) {
lastError = error as Error
if (error instanceof McpError && !error.message.includes('404')) {
break
}
if (index < urlsToTry.length - 1) {
logger.info(
`[${this.config.name}] Retrying with different URL format: ${urlsToTry[index + 1]}`
)
}
}
}
throw lastError || new McpError('All URL variations failed')
}
/**
* Attempt HTTP request
*/
private async attemptHttpRequest(
request: JsonRpcRequest | any,
url: string,
isOriginalUrl = true
): Promise<void> {
if (!isOriginalUrl) {
logger.info(`[${this.config.name}] Trying alternative URL format: ${url}`)
}
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
...this.config.headers,
}
if (this.mcpSessionId) {
headers['Mcp-Session-Id'] = this.mcpSessionId
}
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(request),
})
if (!response.ok) {
const responseText = await response.text().catch(() => 'Could not read response body')
logger.error(`[${this.config.name}] HTTP request failed:`, {
status: response.status,
statusText: response.statusText,
url,
responseBody: responseText.substring(0, 500),
})
throw new McpError(`HTTP request failed: ${response.status} ${response.statusText}`)
}
if ('id' in request) {
const contentType = response.headers.get('Content-Type')
if (contentType?.includes('application/json')) {
const sessionId = response.headers.get('Mcp-Session-Id')
if (sessionId && !this.mcpSessionId) {
this.mcpSessionId = sessionId
logger.info(`[${this.config.name}] Received MCP Session ID: ${sessionId}`)
}
const responseData: JsonRpcResponse = await response.json()
this.handleResponse(responseData)
} else if (contentType?.includes('text/event-stream')) {
const responseText = await response.text()
this.handleSseResponse(responseText, request.id)
} else {
const unexpectedType = contentType || 'unknown'
logger.warn(`[${this.config.name}] Unexpected response content type: ${unexpectedType}`)
const responseText = await response.text()
logger.debug(
`[${this.config.name}] Unexpected response body:`,
responseText.substring(0, 200)
)
throw new McpError(
`Unexpected response content type: ${unexpectedType}. Expected application/json or text/event-stream.`
)
}
}
}
/**
* Handle JSON-RPC response
*/
private handleResponse(response: JsonRpcResponse): void {
const pending = this.pendingRequests.get(response.id)
if (!pending) {
logger.warn(`Received response for unknown request ID: ${response.id}`)
return
}
this.pendingRequests.delete(response.id)
clearTimeout(pending.timeout)
if (response.error) {
const error = new McpError(response.error.message, response.error.code, response.error.data)
pending.reject(error)
} else {
pending.resolve(response.result)
}
}
/**
* Handle Server-Sent Events response format
*/
private handleSseResponse(responseText: string, requestId: string | number): void {
const pending = this.pendingRequests.get(requestId)
if (!pending) {
logger.warn(`Received SSE response for unknown request ID: ${requestId}`)
return
}
try {
// Parse SSE format - look for data: lines
const lines = responseText.split('\n')
let jsonData = ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.substring(6).trim()
if (data && data !== '[DONE]') {
jsonData += data
}
}
}
if (!jsonData) {
logger.error(
`[${this.config.name}] No valid data found in SSE response for request ${requestId}`
)
pending.reject(new McpError('No data in SSE response'))
return
}
// Parse the JSON data
const responseData: JsonRpcResponse = JSON.parse(jsonData)
this.pendingRequests.delete(requestId)
clearTimeout(pending.timeout)
if (responseData.error) {
const error = new McpError(
responseData.error.message,
responseData.error.code,
responseData.error.data
)
pending.reject(error)
} else {
pending.resolve(responseData.result)
}
} catch (error) {
logger.error(`[${this.config.name}] Failed to parse SSE response for request ${requestId}:`, {
error: error instanceof Error ? error.message : 'Unknown error',
responseText: responseText.substring(0, 500),
})
this.pendingRequests.delete(requestId)
clearTimeout(pending.timeout)
pending.reject(new McpError('Failed to parse SSE response'))
logger.error(`[${this.config.name}] Ping failed:`, error)
throw error
}
}
/**
* Check if server has capability
*/
hasCapability(capability: keyof McpCapabilities): boolean {
return !!this.serverCapabilities?.[capability]
hasCapability(capability: string): boolean {
const serverCapabilities = this.client.getServerCapabilities()
return !!serverCapabilities?.[capability]
}
/**
@@ -555,7 +251,8 @@ export class McpClient {
* Get the negotiated protocol version for this connection
*/
getNegotiatedVersion(): string | undefined {
return this.negotiatedVersion
const serverVersion = this.client.getServerVersion()
return typeof serverVersion === 'string' ? serverVersion : undefined
}
/**
@@ -566,10 +263,8 @@ export class McpClient {
return { granted: true, auditId: `audit-${Date.now()}` }
}
// Basic security checks
const { serverId, serverName, action, sideEffects } = consentRequest.context
// Check if server is in blocked
if (this.securityPolicy.blockedOrigins?.includes(this.config.url || '')) {
logger.warn(`Tool execution blocked: Server ${serverName} is in blocked origins`)
return {
@@ -578,7 +273,6 @@ export class McpClient {
}
}
// For high-risk operations, log detailed audit
if (this.securityPolicy.auditLevel === 'detailed') {
logger.info(`Consent requested for ${action} on ${serverName}`, {
serverId,

View File

@@ -264,7 +264,7 @@ class McpService {
id: server.id,
name: server.name,
description: server.description || undefined,
transport: server.transport as 'http' | 'sse',
transport: 'streamable-http' as const,
url: server.url || undefined,
headers: (server.headers as Record<string, string>) || {},
timeout: server.timeout || 30000,

View File

@@ -1,39 +1,10 @@
/**
* Model Context Protocol (MCP) Types
*
* Type definitions for JSON-RPC 2.0 based MCP implementation
* Supporting HTTP/SSE and Streamable HTTP transports
*/
// JSON-RPC 2.0 Base Types
export interface JsonRpcRequest {
jsonrpc: '2.0'
id: string | number
method: string
params?: any
}
export interface JsonRpcResponse<T = any> {
jsonrpc: '2.0'
id: string | number
result?: T
error?: JsonRpcError
}
export interface JsonRpcNotification {
jsonrpc: '2.0'
method: string
params?: any
}
export interface JsonRpcError {
code: number
message: string
data?: any
}
// MCP Transport Types
export type McpTransport = 'http' | 'sse' | 'streamable-http'
// Modern MCP uses Streamable HTTP which handles both HTTP POST and SSE responses
export type McpTransport = 'streamable-http'
export interface McpServerConfig {
id: string
@@ -53,55 +24,12 @@ export interface McpServerConfig {
updatedAt?: string
}
// MCP Protocol Types
export interface McpCapabilities {
tools?: {
listChanged?: boolean
}
resources?: {
subscribe?: boolean
listChanged?: boolean
}
prompts?: {
listChanged?: boolean
}
logging?: Record<string, any>
}
export interface McpInitializeParams {
protocolVersion: string
capabilities: McpCapabilities
clientInfo: {
name: string
version: string
}
}
// Version negotiation support
export interface McpVersionInfo {
supported: string[] // List of supported protocol versions
preferred: string // Preferred version to use
}
export interface McpVersionNegotiationError extends JsonRpcError {
code: -32000 // Custom error code for version negotiation failures
message: 'Version negotiation failed'
data: {
clientVersions: string[]
serverVersions: string[]
reason: string
}
}
export interface McpInitializeResult {
protocolVersion: string
capabilities: McpCapabilities
serverInfo: {
name: string
version: string
}
}
// Security and Consent Framework
export interface McpConsentRequest {
type: 'tool_execution' | 'resource_access' | 'data_sharing'
@@ -166,48 +94,11 @@ export interface McpToolResult {
[key: string]: any
}
// MCP Resource Types
export interface McpResource {
uri: string
name: string
description?: string
mimeType?: string
}
export interface McpResourceContent {
uri: string
mimeType?: string
text?: string
blob?: string
}
// MCP Prompt Types
export interface McpPrompt {
name: string
description?: string
arguments?: Array<{
name: string
description?: string
required?: boolean
}>
}
export interface McpPromptMessage {
role: 'user' | 'assistant'
content: {
type: 'text' | 'image' | 'resource'
text?: string
data?: string
mimeType?: string
}
}
// Connection and Error Types
export interface McpConnectionStatus {
connected: boolean
lastConnected?: Date
lastError?: string
serverInfo?: McpInitializeResult['serverInfo']
}
export class McpError extends Error {
@@ -228,22 +119,6 @@ export class McpConnectionError extends McpError {
}
}
export class McpTimeoutError extends McpError {
constructor(serverId: string, timeout: number) {
super(`MCP request to server ${serverId} timed out after ${timeout}ms`)
this.name = 'McpTimeoutError'
}
}
// Integration Types (for existing platform)
export interface McpToolInput {
type: 'mcp'
serverId: string
toolName: string
params: Record<string, any>
usageControl?: 'auto' | 'force' | 'none'
}
export interface McpServerSummary {
id: string
name: string

View File

@@ -7,9 +7,6 @@ export interface McpServerWithStatus {
transport: McpTransport
url?: string
headers?: Record<string, string>
command?: string
args?: string[]
env?: Record<string, string>
timeout?: number
retries?: number
enabled?: boolean

127
bun.lock
View File

@@ -5,6 +5,7 @@
"name": "simstudio",
"dependencies": {
"@linear/sdk": "40.0.0",
"@modelcontextprotocol/sdk": "1.20.2",
"@t3-oss/env-nextjs": "0.13.4",
"cronstrue": "3.3.0",
"drizzle-orm": "^0.44.5",
@@ -654,7 +655,7 @@
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.18.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.20.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-6rqTdFt67AAAzln3NOKsXRmv5ZzPkgbfaebKBqUbts7vK1GZudqnrun5a8d3M/h955cam9RHZ6Jb4Y1XhnmFPg=="],
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.3.1", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg=="],
@@ -1490,7 +1491,7 @@
"bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="],
"body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
@@ -1620,7 +1621,7 @@
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -1628,7 +1629,7 @@
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
@@ -1862,7 +1863,7 @@
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
@@ -1900,7 +1901,7 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
@@ -1922,7 +1923,7 @@
"framer-motion": ["framer-motion@12.23.22", "", { "dependencies": { "motion-dom": "^12.23.21", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -2282,11 +2283,11 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
@@ -2760,11 +2761,11 @@
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
"serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
@@ -2994,7 +2995,7 @@
"type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
@@ -3188,6 +3189,8 @@
"@browserbasehq/sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"@browserbasehq/stagehand/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.18.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg=="],
"@cerebras/cerebras_cloud_sdk/@types/node": ["@types/node@18.19.128", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-m7wxXGpPpqxp2QDi/rpih5O772APRuBIa/6XiGqLNoM1txkjI8Sz1V4oSXJxQLTz/yP5mgy9z6UXEO6/lP70Gg=="],
"@cerebras/cerebras_cloud_sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
@@ -3202,8 +3205,6 @@
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
@@ -3390,14 +3391,6 @@
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"body-parser/raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"cheerio/htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
@@ -3422,20 +3415,12 @@
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
"express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"fumadocs-mdx/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
@@ -3512,6 +3497,8 @@
"nypm/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
"oauth2-mock-server/express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
"oauth2-mock-server/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"openai/@types/node": ["@types/node@18.19.128", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-m7wxXGpPpqxp2QDi/rpih5O772APRuBIa/6XiGqLNoM1txkjI8Sz1V4oSXJxQLTz/yP5mgy9z6UXEO6/lP70Gg=="],
@@ -3558,10 +3545,6 @@
"samlify/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
"sim/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"sim/lucide-react": ["lucide-react@0.479.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ=="],
@@ -3606,8 +3589,6 @@
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
"vite-tsconfig-paths/tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -3686,26 +3667,6 @@
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"@modelcontextprotocol/sdk/express/send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
@@ -3752,18 +3713,12 @@
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"engine.io/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
@@ -3812,6 +3767,32 @@
"nypm/pkg-types/confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
"oauth2-mock-server/express/body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
"oauth2-mock-server/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"oauth2-mock-server/express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
"oauth2-mock-server/express/cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
"oauth2-mock-server/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"oauth2-mock-server/express/finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
"oauth2-mock-server/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"oauth2-mock-server/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"oauth2-mock-server/express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
"oauth2-mock-server/express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"oauth2-mock-server/express/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
"oauth2-mock-server/express/serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
"oauth2-mock-server/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"openai/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
@@ -3832,8 +3813,6 @@
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"sim/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"sim/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
@@ -3850,8 +3829,6 @@
"test-exclude/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@anthropic-ai/sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"@anthropic-ai/sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -3870,8 +3847,6 @@
"@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
"@trigger.dev/core/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
@@ -3926,6 +3901,18 @@
"log-update/wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"oauth2-mock-server/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"oauth2-mock-server/express/body-parser/raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"oauth2-mock-server/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"oauth2-mock-server/express/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
"oauth2-mock-server/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"oauth2-mock-server/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"openai/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"openai/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -3956,6 +3943,8 @@
"log-update/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"oauth2-mock-server/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"sim/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"lint-staged/listr2/cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],

View File

@@ -35,6 +35,7 @@
},
"dependencies": {
"@linear/sdk": "40.0.0",
"@modelcontextprotocol/sdk": "1.20.2",
"@t3-oss/env-nextjs": "0.13.4",
"cronstrue": "3.3.0",
"drizzle-orm": "^0.44.5",