mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
improvement(mcp): ditch custom mcp client in favor of mcp sdk (#1780)
This commit is contained in:
@@ -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'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
127
bun.lock
@@ -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=="],
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user