Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/sim/app/api/mcp/servers/[id]/refresh/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ export const POST = withMcpAuth<{ id: string }>('read')(
)
} catch (error) {
connectionStatus = 'error'
lastError = error instanceof Error ? error.message : 'Connection test failed'
lastError =
error instanceof Error ? error.message.split('\n')[0].slice(0, 200) : 'Connection failed'
logger.warn(`[${requestId}] Failed to connect to server ${serverId}:`, error)
}

Expand Down
23 changes: 16 additions & 7 deletions apps/sim/app/api/mcp/servers/test-connection/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ interface TestConnectionResult {
warnings?: string[]
}

/**
* Extracts a user-friendly error message from connection errors.
* Keeps diagnostic info (timeout, DNS, HTTP status) but strips
* verbose internals (Zod details, full response bodies, stack traces).
*/
function sanitizeConnectionError(error: unknown): string {
if (!(error instanceof Error)) {
return 'Unknown connection error'
}

const firstLine = error.message.split('\n')[0]
return firstLine.length > 200 ? `${firstLine.slice(0, 200)}...` : firstLine
}

/**
* POST - Test connection to an MCP server before registering it
*/
Expand Down Expand Up @@ -137,8 +151,7 @@ export const POST = withMcpAuth('write')(
} catch (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.error = 'Connection established but could not list tools'
result.warnings = result.warnings || []
result.warnings.push(
'Server connected but tool listing failed - connection may be incomplete'
Expand All @@ -163,11 +176,7 @@ export const POST = withMcpAuth('write')(
logger.warn(`[${requestId}] MCP server test failed:`, error)

result.success = false
if (error instanceof Error) {
result.error = error.message
} else {
result.error = 'Unknown connection error'
}
result.error = sanitizeConnectionError(error)
} finally {
if (client) {
try {
Expand Down
9 changes: 5 additions & 4 deletions apps/sim/app/api/mcp/tools/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ export const POST = withMcpAuth('read')(
tool = tools.find((t) => t.name === toolName) ?? null

if (!tool) {
logger.warn(`[${requestId}] Tool ${toolName} not found on server ${serverId}`, {
availableTools: tools.map((t) => t.name),
})
return createMcpErrorResponse(
new Error(
`Tool ${toolName} not found on server ${serverId}. Available tools: ${tools.map((t) => t.name).join(', ')}`
),
'Tool not found',
new Error('Tool not found'),
'Tool not found on the specified server',
404
)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/cancel-task/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to cancel task',
error: 'Failed to cancel task',
},
{ status: 500 }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to delete push notification',
error: 'Failed to delete push notification',
},
{ status: 500 }
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/get-agent-card/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch Agent Card',
error: 'Failed to fetch Agent Card',
},
{ status: 500 }
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/get-push-notification/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to get push notification',
error: 'Failed to get push notification',
},
{ status: 500 }
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/get-task/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to get task',
error: 'Failed to get task',
},
{ status: 500 }
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/resubscribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to resubscribe',
error: 'Failed to resubscribe',
},
{ status: 500 }
)
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/tools/a2a/send-message/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: `Failed to connect to agent: ${clientError instanceof Error ? clientError.message : 'Unknown error'}`,
error: 'Failed to connect to agent',
},
{ status: 502 }
)
Expand Down Expand Up @@ -158,7 +158,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: `Failed to send message: ${sendError instanceof Error ? sendError.message : 'Unknown error'}`,
error: 'Failed to send message to agent',
},
{ status: 502 }
)
Expand Down Expand Up @@ -218,7 +218,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Internal server error',
error: 'Internal server error',
},
{ status: 500 }
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/a2a/set-push-notification/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to set push notification',
error: 'Failed to set push notification',
},
{ status: 500 }
)
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/app/api/tools/mongodb/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { MongoClient } from 'mongodb'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
import type { MongoDBCollectionInfo, MongoDBConnectionConfig } from '@/tools/mongodb/types'

export async function createMongoDBConnection(config: MongoDBConnectionConfig) {
const hostValidation = await validateDatabaseHost(config.host, 'host')
if (!hostValidation.isValid) {
throw new Error(hostValidation.error)
}

const credentials =
config.username && config.password
? `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@`
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/app/api/tools/mysql/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import mysql from 'mysql2/promise'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'

export interface MySQLConnectionConfig {
host: string
Expand All @@ -10,6 +11,11 @@ export interface MySQLConnectionConfig {
}

export async function createMySQLConnection(config: MySQLConnectionConfig) {
const hostValidation = await validateDatabaseHost(config.host, 'host')
if (!hostValidation.isValid) {
throw new Error(hostValidation.error)
}

const connectionConfig: mysql.ConnectionOptions = {
host: config.host,
port: config.port,
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/app/api/tools/neo4j/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import neo4j from 'neo4j-driver'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
import type { Neo4jConnectionConfig } from '@/tools/neo4j/types'

export async function createNeo4jDriver(config: Neo4jConnectionConfig) {
const hostValidation = await validateDatabaseHost(config.host, 'host')
if (!hostValidation.isValid) {
throw new Error(hostValidation.error)
}

const isAuraHost =
config.host === 'databases.neo4j.io' || config.host.endsWith('.databases.neo4j.io')

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/delete/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function POST(request: NextRequest) {
`[${requestId}] Deleting data from ${params.table} on ${params.host}:${params.port}/${params.database}`
)

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function POST(request: NextRequest) {
)
}

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/insert/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function POST(request: NextRequest) {
`[${requestId}] Inserting data into ${params.table} on ${params.host}:${params.port}/${params.database}`
)

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/introspect/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function POST(request: NextRequest) {
`[${requestId}] Introspecting PostgreSQL schema on ${params.host}:${params.port}/${params.database}`
)

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/query/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function POST(request: NextRequest) {
`[${requestId}] Executing PostgreSQL query on ${params.host}:${params.port}/${params.database}`
)

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/tools/postgresql/update/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function POST(request: NextRequest) {
`[${requestId}] Updating data in ${params.table} on ${params.host}:${params.port}/${params.database}`
)

const sql = createPostgresConnection({
const sql = await createPostgresConnection({
host: params.host,
port: params.port,
database: params.database,
Expand Down
8 changes: 7 additions & 1 deletion apps/sim/app/api/tools/postgresql/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import postgres from 'postgres'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
import type { PostgresConnectionConfig } from '@/tools/postgresql/types'

export function createPostgresConnection(config: PostgresConnectionConfig) {
export async function createPostgresConnection(config: PostgresConnectionConfig) {
const hostValidation = await validateDatabaseHost(config.host, 'host')
if (!hostValidation.isValid) {
throw new Error(hostValidation.error)
}

const sslConfig =
config.ssl === 'disabled'
? false
Expand Down
11 changes: 11 additions & 0 deletions apps/sim/app/api/tools/redis/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Redis from 'ioredis'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'

const logger = createLogger('RedisAPI')

Expand All @@ -24,6 +25,16 @@ export async function POST(request: NextRequest) {
const body = await request.json()
const { url, command, args } = RequestSchema.parse(body)

const parsedUrl = new URL(url)
const hostname =
parsedUrl.hostname.startsWith('[') && parsedUrl.hostname.endsWith(']')
? parsedUrl.hostname.slice(1, -1)
: parsedUrl.hostname
const hostValidation = await validateDatabaseHost(hostname, 'host')
if (!hostValidation.isValid) {
return NextResponse.json({ error: hostValidation.error }, { status: 400 })
}

client = new Redis(url, {
connectTimeout: 10000,
commandTimeout: 10000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/components/emails'
import { getSession } from '@/lib/auth'
import { decryptSecret } from '@/lib/core/security/encryption'
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
Expand Down Expand Up @@ -135,18 +136,18 @@ async function testWebhook(subscription: typeof workspaceNotificationSubscriptio
headers['sim-signature'] = `t=${timestamp},v1=${signature}`
}

const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000)

try {
const response = await fetch(webhookConfig.url, {
method: 'POST',
headers,
body,
signal: controller.signal,
})

clearTimeout(timeoutId)
const response = await secureFetchWithValidation(
webhookConfig.url,
{
method: 'POST',
headers,
body,
timeout: 10000,
allowHttp: true,
},
'webhookUrl'
)
const responseBody = await response.text().catch(() => '')

return {
Expand All @@ -157,12 +158,10 @@ async function testWebhook(subscription: typeof workspaceNotificationSubscriptio
timestamp: new Date().toISOString(),
}
} catch (error: unknown) {
clearTimeout(timeoutId)
const err = error as Error & { name?: string }
if (err.name === 'AbortError') {
return { success: false, error: 'Request timeout after 10 seconds' }
}
return { success: false, error: err.message }
logger.warn('Webhook test failed', {
error: error instanceof Error ? error.message : String(error),
})
return { success: false, error: 'Failed to deliver webhook' }
}
}

Expand Down Expand Up @@ -268,13 +267,15 @@ async function testSlack(

return {
success: result.ok,
error: result.error,
error: result.ok ? undefined : `Slack error: ${result.error || 'unknown'}`,
channel: result.channel,
timestamp: new Date().toISOString(),
}
} catch (error: unknown) {
const err = error as Error
return { success: false, error: err.message }
logger.warn('Slack test notification failed', {
error: error instanceof Error ? error.message : String(error),
})
return { success: false, error: 'Failed to send Slack notification' }
}
}

Expand Down
Loading