Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
29 changes: 22 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,26 @@ 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 msg = error.message

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

return msg
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
}

/**
* POST - Test connection to an MCP server before registering it
*/
Expand Down Expand Up @@ -137,8 +157,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 +182,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)
}
Comment thread
waleedlatif1 marked this conversation as resolved.

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 })
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

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