feat(tools): add MongoDB (#1225)

* added mongo, haven't tested

* fixed bugs, refined prompts, added billing for wand if billing enabled

* add docs

* ack PR comments
This commit is contained in:
Waleed
2025-09-02 18:55:45 -07:00
committed by GitHub
parent c2d668c3eb
commit 533b4c53e0
27 changed files with 2849 additions and 50 deletions

View File

@@ -33,6 +33,7 @@
"microsoft_planner",
"microsoft_teams",
"mistral_parse",
"mongodb",
"mysql",
"notion",
"onedrive",

View File

@@ -0,0 +1,264 @@
---
title: MongoDB
description: Connect to MongoDB database
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="mongodb"
color="#E0E0E0"
icon={true}
iconSvg={`<svg className="block-icon" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='currentColor'
d='M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#45A538'
d='M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#46A037'
d='M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#409433'
d='M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4FAA41'
d='M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4AA73C'
d='M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#57AE47'
d='M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#60B24F'
d='M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#A9AA88'
d='M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#B6B598'
d='M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#C2C1A7'
d='M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#CECDB7'
d='M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#DBDAC7'
d='M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#EBE9DC'
d='M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#CECDB7'
d='M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4FAA41'
d='M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z'
/>
</svg>`}
/>
## Usage Instructions
Connect to any MongoDB database to execute queries, manage data, and perform database operations. Supports find, insert, update, delete, and aggregation operations with secure connection handling.
## Tools
### `mongodb_query`
Execute find operation on MongoDB collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | MongoDB server hostname or IP address |
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
| `database` | string | Yes | Database name to connect to |
| `username` | string | No | MongoDB username |
| `password` | string | No | MongoDB password |
| `authSource` | string | No | Authentication database |
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
| `collection` | string | Yes | Collection name to query |
| `query` | string | No | MongoDB query filter as JSON string |
| `limit` | number | No | Maximum number of documents to return |
| `sort` | string | No | Sort criteria as JSON string |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `documents` | array | Array of documents returned from the query |
| `documentCount` | number | Number of documents returned |
### `mongodb_insert`
Insert documents into MongoDB collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | MongoDB server hostname or IP address |
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
| `database` | string | Yes | Database name to connect to |
| `username` | string | No | MongoDB username |
| `password` | string | No | MongoDB password |
| `authSource` | string | No | Authentication database |
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
| `collection` | string | Yes | Collection name to insert into |
| `documents` | array | Yes | Array of documents to insert |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `documentCount` | number | Number of documents inserted |
| `insertedId` | string | ID of inserted document \(single insert\) |
| `insertedIds` | array | Array of inserted document IDs \(multiple insert\) |
### `mongodb_update`
Update documents in MongoDB collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | MongoDB server hostname or IP address |
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
| `database` | string | Yes | Database name to connect to |
| `username` | string | No | MongoDB username |
| `password` | string | No | MongoDB password |
| `authSource` | string | No | Authentication database |
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
| `collection` | string | Yes | Collection name to update |
| `filter` | string | Yes | Filter criteria as JSON string |
| `update` | string | Yes | Update operations as JSON string |
| `upsert` | boolean | No | Create document if not found |
| `multi` | boolean | No | Update multiple documents |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `matchedCount` | number | Number of documents matched by filter |
| `modifiedCount` | number | Number of documents modified |
| `documentCount` | number | Total number of documents affected |
| `insertedId` | string | ID of inserted document \(if upsert\) |
### `mongodb_delete`
Delete documents from MongoDB collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | MongoDB server hostname or IP address |
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
| `database` | string | Yes | Database name to connect to |
| `username` | string | No | MongoDB username |
| `password` | string | No | MongoDB password |
| `authSource` | string | No | Authentication database |
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
| `collection` | string | Yes | Collection name to delete from |
| `filter` | string | Yes | Filter criteria as JSON string |
| `multi` | boolean | No | Delete multiple documents |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `deletedCount` | number | Number of documents deleted |
| `documentCount` | number | Total number of documents affected |
### `mongodb_execute`
Execute MongoDB aggregation pipeline
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | MongoDB server hostname or IP address |
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
| `database` | string | Yes | Database name to connect to |
| `username` | string | No | MongoDB username |
| `password` | string | No | MongoDB password |
| `authSource` | string | No | Authentication database |
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
| `collection` | string | Yes | Collection name to execute pipeline on |
| `pipeline` | string | Yes | Aggregation pipeline as JSON string |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `documents` | array | Array of documents returned from aggregation |
| `documentCount` | number | Number of documents returned |
## Notes
- Category: `tools`
- Type: `mongodb`

View File

@@ -0,0 +1,114 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBDeleteAPI')
const DeleteSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive('Port must be a positive integer'),
database: z.string().min(1, 'Database name is required'),
username: z.string().min(1, 'Username is required'),
password: z.string().min(1, 'Password is required'),
authSource: z.string().optional(),
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
collection: z.string().min(1, 'Collection name is required'),
filter: z
.union([z.string(), z.object({}).passthrough()])
.transform((val) => {
if (typeof val === 'object' && val !== null) {
return JSON.stringify(val)
}
return val
})
.refine((val) => val && val.trim() !== '' && val !== '{}', {
message: 'Filter is required for MongoDB Delete',
}),
multi: z
.union([z.boolean(), z.string(), z.undefined()])
.optional()
.transform((val) => {
if (val === 'true' || val === true) return true
if (val === 'false' || val === false) return false
return false // Default to false
}),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
let client = null
try {
const body = await request.json()
const params = DeleteSchema.parse(body)
logger.info(
`[${requestId}] Deleting document(s) from ${params.host}:${params.port}/${params.database}.${params.collection} (multi: ${params.multi})`
)
const sanitizedCollection = sanitizeCollectionName(params.collection)
const filterValidation = validateFilter(params.filter)
if (!filterValidation.isValid) {
logger.warn(`[${requestId}] Filter validation failed: ${filterValidation.error}`)
return NextResponse.json(
{ error: `Filter validation failed: ${filterValidation.error}` },
{ status: 400 }
)
}
let filterDoc
try {
filterDoc = JSON.parse(params.filter)
} catch (error) {
logger.warn(`[${requestId}] Invalid filter JSON: ${params.filter}`)
return NextResponse.json({ error: 'Invalid JSON format in filter' }, { status: 400 })
}
client = await createMongoDBConnection({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl,
})
const db = client.db(params.database)
const coll = db.collection(sanitizedCollection)
let result
if (params.multi) {
result = await coll.deleteMany(filterDoc)
} else {
result = await coll.deleteOne(filterDoc)
}
logger.info(`[${requestId}] Delete completed: ${result.deletedCount} documents deleted`)
return NextResponse.json({
message: `${result.deletedCount} documents deleted`,
deletedCount: result.deletedCount,
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] MongoDB delete failed:`, error)
return NextResponse.json({ error: `MongoDB delete failed: ${errorMessage}` }, { status: 500 })
} finally {
if (client) {
await client.close()
}
}
}

View File

@@ -0,0 +1,102 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createMongoDBConnection, sanitizeCollectionName, validatePipeline } from '../utils'
const logger = createLogger('MongoDBExecuteAPI')
const ExecuteSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive('Port must be a positive integer'),
database: z.string().min(1, 'Database name is required'),
username: z.string().min(1, 'Username is required'),
password: z.string().min(1, 'Password is required'),
authSource: z.string().optional(),
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
collection: z.string().min(1, 'Collection name is required'),
pipeline: z
.union([z.string(), z.array(z.object({}).passthrough())])
.transform((val) => {
if (Array.isArray(val)) {
return JSON.stringify(val)
}
return val
})
.refine((val) => val && val.trim() !== '', {
message: 'Pipeline is required',
}),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
let client = null
try {
const body = await request.json()
const params = ExecuteSchema.parse(body)
logger.info(
`[${requestId}] Executing aggregation pipeline on ${params.host}:${params.port}/${params.database}.${params.collection}`
)
const sanitizedCollection = sanitizeCollectionName(params.collection)
const pipelineValidation = validatePipeline(params.pipeline)
if (!pipelineValidation.isValid) {
logger.warn(`[${requestId}] Pipeline validation failed: ${pipelineValidation.error}`)
return NextResponse.json(
{ error: `Pipeline validation failed: ${pipelineValidation.error}` },
{ status: 400 }
)
}
const pipelineDoc = JSON.parse(params.pipeline)
client = await createMongoDBConnection({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl,
})
const db = client.db(params.database)
const coll = db.collection(sanitizedCollection)
const cursor = coll.aggregate(pipelineDoc)
const documents = await cursor.toArray()
logger.info(
`[${requestId}] Aggregation completed successfully, returned ${documents.length} documents`
)
return NextResponse.json({
message: `Aggregation completed, returned ${documents.length} documents`,
documents,
documentCount: documents.length,
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] MongoDB aggregation failed:`, error)
return NextResponse.json(
{ error: `MongoDB aggregation failed: ${errorMessage}` },
{ status: 500 }
)
} finally {
if (client) {
await client.close()
}
}
}

View File

@@ -0,0 +1,98 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createMongoDBConnection, sanitizeCollectionName } from '../utils'
const logger = createLogger('MongoDBInsertAPI')
const InsertSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive('Port must be a positive integer'),
database: z.string().min(1, 'Database name is required'),
username: z.string().min(1, 'Username is required'),
password: z.string().min(1, 'Password is required'),
authSource: z.string().optional(),
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
collection: z.string().min(1, 'Collection name is required'),
documents: z
.union([z.array(z.record(z.unknown())), z.string()])
.transform((val) => {
if (typeof val === 'string') {
try {
const parsed = JSON.parse(val)
return Array.isArray(parsed) ? parsed : [parsed]
} catch {
throw new Error('Invalid JSON in documents field')
}
}
return val
})
.refine((val) => Array.isArray(val) && val.length > 0, {
message: 'At least one document is required',
}),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
let client = null
try {
const body = await request.json()
const params = InsertSchema.parse(body)
logger.info(
`[${requestId}] Inserting ${params.documents.length} document(s) into ${params.host}:${params.port}/${params.database}.${params.collection}`
)
const sanitizedCollection = sanitizeCollectionName(params.collection)
client = await createMongoDBConnection({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl,
})
const db = client.db(params.database)
const coll = db.collection(sanitizedCollection)
let result
if (params.documents.length === 1) {
result = await coll.insertOne(params.documents[0] as Record<string, unknown>)
logger.info(`[${requestId}] Single document inserted successfully`)
return NextResponse.json({
message: 'Document inserted successfully',
insertedId: result.insertedId.toString(),
documentCount: 1,
})
}
result = await coll.insertMany(params.documents as Record<string, unknown>[])
const insertedCount = Object.keys(result.insertedIds).length
logger.info(`[${requestId}] ${insertedCount} documents inserted successfully`)
return NextResponse.json({
message: `${insertedCount} documents inserted successfully`,
insertedIds: Object.values(result.insertedIds).map((id) => id.toString()),
documentCount: insertedCount,
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] MongoDB insert failed:`, error)
return NextResponse.json({ error: `MongoDB insert failed: ${errorMessage}` }, { status: 500 })
} finally {
if (client) {
await client.close()
}
}
}

View File

@@ -0,0 +1,136 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBQueryAPI')
const QuerySchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive('Port must be a positive integer'),
database: z.string().min(1, 'Database name is required'),
username: z.string().min(1, 'Username is required'),
password: z.string().min(1, 'Password is required'),
authSource: z.string().optional(),
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
collection: z.string().min(1, 'Collection name is required'),
query: z
.union([z.string(), z.object({}).passthrough()])
.optional()
.default('{}')
.transform((val) => {
if (typeof val === 'object' && val !== null) {
return JSON.stringify(val)
}
return val || '{}'
}),
limit: z
.union([z.coerce.number().int().positive(), z.literal(''), z.undefined()])
.optional()
.transform((val) => {
if (val === '' || val === undefined || val === null) {
return 100
}
return val
}),
sort: z
.union([z.string(), z.object({}).passthrough(), z.null()])
.optional()
.transform((val) => {
if (typeof val === 'object' && val !== null) {
return JSON.stringify(val)
}
return val
}),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
let client = null
try {
const body = await request.json()
const params = QuerySchema.parse(body)
logger.info(
`[${requestId}] Executing MongoDB query on ${params.host}:${params.port}/${params.database}.${params.collection}`
)
const sanitizedCollection = sanitizeCollectionName(params.collection)
let filter = {}
if (params.query?.trim()) {
const validation = validateFilter(params.query)
if (!validation.isValid) {
logger.warn(`[${requestId}] Filter validation failed: ${validation.error}`)
return NextResponse.json(
{ error: `Filter validation failed: ${validation.error}` },
{ status: 400 }
)
}
filter = JSON.parse(params.query)
}
let sortCriteria = {}
if (params.sort?.trim()) {
try {
sortCriteria = JSON.parse(params.sort)
} catch (error) {
logger.warn(`[${requestId}] Invalid sort JSON: ${params.sort}`)
return NextResponse.json({ error: 'Invalid JSON format in sort criteria' }, { status: 400 })
}
}
client = await createMongoDBConnection({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl,
})
const db = client.db(params.database)
const coll = db.collection(sanitizedCollection)
let cursor = coll.find(filter)
if (Object.keys(sortCriteria).length > 0) {
cursor = cursor.sort(sortCriteria)
}
const limit = params.limit || 100
cursor = cursor.limit(limit)
const documents = await cursor.toArray()
logger.info(
`[${requestId}] Query executed successfully, returned ${documents.length} documents`
)
return NextResponse.json({
message: `Found ${documents.length} documents`,
documents,
documentCount: documents.length,
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] MongoDB query failed:`, error)
return NextResponse.json({ error: `MongoDB query failed: ${errorMessage}` }, { status: 500 })
} finally {
if (client) {
await client.close()
}
}
}

View File

@@ -0,0 +1,143 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBUpdateAPI')
const UpdateSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive('Port must be a positive integer'),
database: z.string().min(1, 'Database name is required'),
username: z.string().min(1, 'Username is required'),
password: z.string().min(1, 'Password is required'),
authSource: z.string().optional(),
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
collection: z.string().min(1, 'Collection name is required'),
filter: z
.union([z.string(), z.object({}).passthrough()])
.transform((val) => {
if (typeof val === 'object' && val !== null) {
return JSON.stringify(val)
}
return val
})
.refine((val) => val && val.trim() !== '' && val !== '{}', {
message: 'Filter is required for MongoDB Update',
}),
update: z
.union([z.string(), z.object({}).passthrough()])
.transform((val) => {
if (typeof val === 'object' && val !== null) {
return JSON.stringify(val)
}
return val
})
.refine((val) => val && val.trim() !== '', {
message: 'Update is required',
}),
upsert: z
.union([z.boolean(), z.string(), z.undefined()])
.optional()
.transform((val) => {
if (val === 'true' || val === true) return true
if (val === 'false' || val === false) return false
return false
}),
multi: z
.union([z.boolean(), z.string(), z.undefined()])
.optional()
.transform((val) => {
if (val === 'true' || val === true) return true
if (val === 'false' || val === false) return false
return false
}),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
let client = null
try {
const body = await request.json()
const params = UpdateSchema.parse(body)
logger.info(
`[${requestId}] Updating document(s) in ${params.host}:${params.port}/${params.database}.${params.collection} (multi: ${params.multi}, upsert: ${params.upsert})`
)
const sanitizedCollection = sanitizeCollectionName(params.collection)
const filterValidation = validateFilter(params.filter)
if (!filterValidation.isValid) {
logger.warn(`[${requestId}] Filter validation failed: ${filterValidation.error}`)
return NextResponse.json(
{ error: `Filter validation failed: ${filterValidation.error}` },
{ status: 400 }
)
}
let filterDoc
let updateDoc
try {
filterDoc = JSON.parse(params.filter)
updateDoc = JSON.parse(params.update)
} catch (error) {
logger.warn(`[${requestId}] Invalid JSON in filter or update`)
return NextResponse.json(
{ error: 'Invalid JSON format in filter or update' },
{ status: 400 }
)
}
client = await createMongoDBConnection({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl,
})
const db = client.db(params.database)
const coll = db.collection(sanitizedCollection)
let result
if (params.multi) {
result = await coll.updateMany(filterDoc, updateDoc, { upsert: params.upsert })
} else {
result = await coll.updateOne(filterDoc, updateDoc, { upsert: params.upsert })
}
logger.info(
`[${requestId}] Update completed: ${result.modifiedCount} modified, ${result.matchedCount} matched${result.upsertedCount ? `, ${result.upsertedCount} upserted` : ''}`
)
return NextResponse.json({
message: `${result.modifiedCount} documents updated${result.upsertedCount ? `, ${result.upsertedCount} documents upserted` : ''}`,
matchedCount: result.matchedCount,
modifiedCount: result.modifiedCount,
documentCount: result.modifiedCount + (result.upsertedCount || 0),
...(result.upsertedId && { insertedId: result.upsertedId.toString() }),
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] MongoDB update failed:`, error)
return NextResponse.json({ error: `MongoDB update failed: ${errorMessage}` }, { status: 500 })
} finally {
if (client) {
await client.close()
}
}
}

View File

@@ -0,0 +1,123 @@
import { MongoClient } from 'mongodb'
import type { MongoDBConnectionConfig } from '@/tools/mongodb/types'
export async function createMongoDBConnection(config: MongoDBConnectionConfig) {
const credentials =
config.username && config.password
? `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@`
: ''
const queryParams = new URLSearchParams()
if (config.authSource) {
queryParams.append('authSource', config.authSource)
}
if (config.ssl === 'required') {
queryParams.append('ssl', 'true')
}
const queryString = queryParams.toString()
const uri = `mongodb://${credentials}${config.host}:${config.port}/${config.database}${queryString ? `?${queryString}` : ''}`
const client = new MongoClient(uri, {
connectTimeoutMS: 10000,
socketTimeoutMS: 10000,
maxPoolSize: 1,
})
await client.connect()
return client
}
export function validateFilter(filter: string): { isValid: boolean; error?: string } {
try {
const parsed = JSON.parse(filter)
const dangerousOperators = ['$where', '$regex', '$expr', '$function', '$accumulator', '$let']
const checkForDangerousOps = (obj: any): boolean => {
if (typeof obj !== 'object' || obj === null) return false
for (const key of Object.keys(obj)) {
if (dangerousOperators.includes(key)) return true
if (typeof obj[key] === 'object' && checkForDangerousOps(obj[key])) return true
}
return false
}
if (checkForDangerousOps(parsed)) {
return {
isValid: false,
error: 'Filter contains potentially dangerous operators',
}
}
return { isValid: true }
} catch (error) {
return {
isValid: false,
error: 'Invalid JSON format in filter',
}
}
}
export function validatePipeline(pipeline: string): { isValid: boolean; error?: string } {
try {
const parsed = JSON.parse(pipeline)
if (!Array.isArray(parsed)) {
return {
isValid: false,
error: 'Pipeline must be an array',
}
}
const dangerousOperators = [
'$where',
'$function',
'$accumulator',
'$let',
'$merge',
'$out',
'$currentOp',
'$listSessions',
'$listLocalSessions',
]
const checkPipelineStage = (stage: any): boolean => {
if (typeof stage !== 'object' || stage === null) return false
for (const key of Object.keys(stage)) {
if (dangerousOperators.includes(key)) return true
if (typeof stage[key] === 'object' && checkPipelineStage(stage[key])) return true
}
return false
}
for (const stage of parsed) {
if (checkPipelineStage(stage)) {
return {
isValid: false,
error: 'Pipeline contains potentially dangerous operators',
}
}
}
return { isValid: true }
} catch (error) {
return {
isValid: false,
error: 'Invalid JSON format in pipeline',
}
}
}
export function sanitizeCollectionName(name: string): string {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
throw new Error(
'Invalid collection name. Must start with letter or underscore and contain only letters, numbers, and underscores.'
)
}
return name
}

View File

@@ -1,7 +1,12 @@
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import OpenAI, { AzureOpenAI } from 'openai'
import { env } from '@/lib/env'
import { getCostMultiplier, isBillingEnabled } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { db } from '@/db'
import { userStats, workflow } from '@/db/schema'
import { getModelPricing } from '@/providers/utils'
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
@@ -47,6 +52,7 @@ interface RequestBody {
systemPrompt?: string
stream?: boolean
history?: ChatMessage[]
workflowId?: string
}
function safeStringify(value: unknown): string {
@@ -57,6 +63,80 @@ function safeStringify(value: unknown): string {
}
}
async function updateUserStatsForWand(
workflowId: string,
usage: {
prompt_tokens?: number
completion_tokens?: number
total_tokens?: number
},
requestId: string
): Promise<void> {
if (!isBillingEnabled) {
logger.debug(`[${requestId}] Billing is disabled, skipping wand usage cost update`)
return
}
if (!usage.total_tokens || usage.total_tokens <= 0) {
logger.debug(`[${requestId}] No tokens to update in user stats`)
return
}
try {
const [workflowRecord] = await db
.select({ userId: workflow.userId })
.from(workflow)
.where(eq(workflow.id, workflowId))
.limit(1)
if (!workflowRecord?.userId) {
logger.warn(
`[${requestId}] No user found for workflow ${workflowId}, cannot update user stats`
)
return
}
const userId = workflowRecord.userId
const totalTokens = usage.total_tokens || 0
const promptTokens = usage.prompt_tokens || 0
const completionTokens = usage.completion_tokens || 0
const modelName = useWandAzure ? wandModelName : 'gpt-4o'
const pricing = getModelPricing(modelName)
const costMultiplier = getCostMultiplier()
let modelCost = 0
if (pricing) {
const inputCost = (promptTokens / 1000000) * pricing.input
const outputCost = (completionTokens / 1000000) * pricing.output
modelCost = inputCost + outputCost
} else {
modelCost = (promptTokens / 1000000) * 0.005 + (completionTokens / 1000000) * 0.015
}
const costToStore = modelCost * costMultiplier
await db
.update(userStats)
.set({
totalTokensUsed: sql`total_tokens_used + ${totalTokens}`,
totalCost: sql`total_cost + ${costToStore}`,
currentPeriodCost: sql`current_period_cost + ${costToStore}`,
lastActive: new Date(),
})
.where(eq(userStats.userId, userId))
logger.debug(`[${requestId}] Updated user stats for wand usage`, {
userId,
tokensUsed: totalTokens,
costAdded: costToStore,
})
} catch (error) {
logger.error(`[${requestId}] Failed to update user stats for wand usage`, error)
}
}
export async function POST(req: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
logger.info(`[${requestId}] Received wand generation request`)
@@ -72,7 +152,7 @@ export async function POST(req: NextRequest) {
try {
const body = (await req.json()) as RequestBody
const { prompt, systemPrompt, stream = false, history = [] } = body
const { prompt, systemPrompt, stream = false, history = [], workflowId } = body
if (!prompt) {
logger.warn(`[${requestId}] Invalid request: Missing prompt.`)
@@ -135,7 +215,7 @@ export async function POST(req: NextRequest) {
body: JSON.stringify({
model: useWandAzure ? wandModelName : 'gpt-4o',
messages: messages,
temperature: 0.3,
temperature: 0.2,
max_tokens: 10000,
stream: true,
stream_options: { include_usage: true },
@@ -168,6 +248,7 @@ export async function POST(req: NextRequest) {
try {
let buffer = ''
let chunkCount = 0
let finalUsage: any = null
while (true) {
const { done, value } = await reader.read()
@@ -213,6 +294,7 @@ export async function POST(req: NextRequest) {
}
if (parsed.usage) {
finalUsage = parsed.usage
logger.info(
`[${requestId}] Received usage data: ${JSON.stringify(parsed.usage)}`
)
@@ -231,6 +313,10 @@ export async function POST(req: NextRequest) {
}
logger.info(`[${requestId}] Wand generation streaming completed successfully`)
if (finalUsage && workflowId) {
await updateUserStatsForWand(workflowId, finalUsage, requestId)
}
} catch (streamError: any) {
logger.error(`[${requestId}] Streaming error`, {
name: streamError?.name,
@@ -297,6 +383,11 @@ export async function POST(req: NextRequest) {
}
logger.info(`[${requestId}] Wand generation successful`)
if (completion.usage && workflowId) {
await updateUserStatsForWand(workflowId, completion.usage, requestId)
}
return NextResponse.json({ success: true, content: generatedContent })
} catch (error: any) {
logger.error(`[${requestId}] Wand generation failed`, {

View File

@@ -179,20 +179,13 @@ IMPORTANT FORMATTING RULES:
return wandConfig
}, [wandConfig, remoteExecution, languageValue])
const wandHook = dynamicWandConfig?.enabled
? useWand({
wandConfig: dynamicWandConfig,
currentValue: code,
onStreamStart: () => handleStreamStartRef.current?.(),
onStreamChunk: (chunk: string) => {
setCode((prev) => prev + chunk)
handleStreamChunkRef.current?.(chunk)
},
onGeneratedContent: (content: string) => {
handleGeneratedContentRef.current?.(content)
},
})
: null
const wandHook = useWand({
wandConfig: wandConfig || { enabled: false, prompt: '' },
currentValue: code,
onStreamStart: () => handleStreamStartRef.current?.(),
onStreamChunk: (chunk: string) => handleStreamChunkRef.current?.(chunk),
onGeneratedContent: (content: string) => handleGeneratedContentRef.current?.(content),
})
const isAiLoading = wandHook?.isLoading || false
const isAiStreaming = wandHook?.isStreaming || false

View File

@@ -198,7 +198,6 @@ export function useWand({
const { done, value } = await reader.read()
if (done) break
// Process incoming chunks using SSE format (identical to Chat panel)
const chunk = decoder.decode(value)
const lines = chunk.split('\n\n')
@@ -207,26 +206,21 @@ export function useWand({
try {
const data = JSON.parse(line.substring(6))
// Check if there's an error
if (data.error) {
throw new Error(data.error)
}
// Process chunk
if (data.chunk) {
accumulatedContent += data.chunk
// Stream each chunk to the UI immediately
if (onStreamChunk) {
onStreamChunk(data.chunk)
}
}
// Check if streaming is complete
if (data.done) {
break
}
} catch (parseError) {
// Continue processing other lines
logger.debug('Failed to parse SSE line', { line, parseError })
}
}
@@ -239,7 +233,6 @@ export function useWand({
if (accumulatedContent) {
onGeneratedContent(accumulatedContent)
// Update conversation history if enabled
if (wandConfig.maintainHistory) {
setConversationHistory((prev) => [
...prev,
@@ -248,7 +241,6 @@ export function useWand({
])
}
// Call completion callback
if (onGenerationComplete) {
onGenerationComplete(currentPrompt, accumulatedContent)
}

View File

@@ -556,7 +556,7 @@ const WorkflowContent = React.memo(() => {
let autoConnectEdge
if (isAutoConnectEnabled && type !== 'starter') {
const closestBlock = findClosestOutput(centerPosition)
logger.info('🎯 Closest block found:', closestBlock)
logger.info('Closest block found:', closestBlock)
if (closestBlock) {
// Get appropriate source handle
const sourceHandle = determineSourceHandle(closestBlock)
@@ -569,7 +569,7 @@ const WorkflowContent = React.memo(() => {
targetHandle: 'target',
type: 'workflowEdge',
}
logger.info('Auto-connect edge created:', autoConnectEdge)
logger.info('Auto-connect edge created:', autoConnectEdge)
}
}

View File

@@ -0,0 +1,960 @@
import { MongoDBIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { MongoDBResponse } from '@/tools/mongodb/types'
export const MongoDBBlock: BlockConfig<MongoDBResponse> = {
type: 'mongodb',
name: 'MongoDB',
description: 'Connect to MongoDB database',
longDescription:
'Connect to any MongoDB database to execute queries, manage data, and perform database operations. Supports find, insert, update, delete, and aggregation operations with secure connection handling.',
docsLink: 'https://docs.sim.ai/tools/mongodb',
category: 'tools',
bgColor: '#E0E0E0',
icon: MongoDBIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Find Documents', id: 'query' },
{ label: 'Insert Documents', id: 'insert' },
{ label: 'Update Documents', id: 'update' },
{ label: 'Delete Documents', id: 'delete' },
{ label: 'Aggregate Pipeline', id: 'execute' },
],
value: () => 'query',
},
{
id: 'host',
title: 'Host',
type: 'short-input',
layout: 'full',
placeholder: 'localhost or your.mongodb.host',
required: true,
},
{
id: 'port',
title: 'Port',
type: 'short-input',
layout: 'full',
placeholder: '27017',
value: () => '27017',
required: true,
},
{
id: 'database',
title: 'Database Name',
type: 'short-input',
layout: 'full',
placeholder: 'your_database',
required: true,
},
{
id: 'username',
title: 'Username',
type: 'short-input',
layout: 'full',
placeholder: 'mongodb_user',
required: true,
},
{
id: 'password',
title: 'Password',
type: 'short-input',
layout: 'full',
password: true,
placeholder: 'Your database password',
required: true,
},
{
id: 'authSource',
title: 'Auth Source',
type: 'short-input',
layout: 'full',
placeholder: 'admin',
},
{
id: 'ssl',
title: 'SSL Mode',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Disabled', id: 'disabled' },
{ label: 'Required', id: 'required' },
{ label: 'Preferred', id: 'preferred' },
],
value: () => 'preferred',
},
{
id: 'collection',
title: 'Collection Name',
type: 'short-input',
layout: 'full',
placeholder: 'users',
required: true,
},
{
id: 'query',
title: 'Query Filter (JSON)',
type: 'code',
layout: 'full',
placeholder: '{"status": "active"}',
condition: { field: 'operation', value: 'query' },
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects based on the user's request.
### CONTEXT
{context}
### CRITICAL INSTRUCTION
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that can be used directly in a MongoDB find() operation.
### FILTER GUIDELINES
1. **Syntax**: Use MongoDB query operators and proper JSON syntax
2. **Performance**: Consider indexing and query optimization
3. **Security**: Use safe query patterns, avoid NoSQL injection risks
4. **Data Types**: Use appropriate MongoDB data types (ObjectId, Date, etc.)
5. **Efficiency**: Structure filters for optimal query execution
### MONGODB QUERY OPERATORS
**Comparison Operators**:
- **$eq**: Equals - \`{"status": {"$eq": "active"}}\` or \`{"status": "active"}\`
- **$ne**: Not equals - \`{"status": {"$ne": "inactive"}}\`
- **$gt**: Greater than - \`{"age": {"$gt": 18}}\`
- **$gte**: Greater than or equal - \`{"price": {"$gte": 100}}\`
- **$lt**: Less than - \`{"score": {"$lt": 90}}\`
- **$lte**: Less than or equal - \`{"rating": {"$lte": 5}}\`
- **$in**: In array - \`{"category": {"$in": ["tech", "science"]}}\`
- **$nin**: Not in array - \`{"status": {"$nin": ["deleted", "banned"]}}\`
**Logical Operators**:
- **$and**: AND condition - \`{"$and": [{"age": {"$gte": 18}}, {"status": "active"}]}\`
- **$or**: OR condition - \`{"$or": [{"status": "active"}, {"status": "pending"}]}\`
- **$not**: NOT condition - \`{"age": {"$not": {"$lt": 18}}}\`
- **$nor**: NOR condition - \`{"$nor": [{"status": "deleted"}, {"verified": false}]}\`
**Element Operators**:
- **$exists**: Field exists - \`{"email": {"$exists": true}}\`
- **$type**: Field type - \`{"count": {"$type": "number"}}\`
**Array Operators**:
- **$all**: All elements match - \`{"tags": {"$all": ["tech", "mongodb"]}}\`
- **$elemMatch**: Element matches - \`{"scores": {"$elemMatch": {"$gte": 80, "$lt": 90}}}\`
- **$size**: Array size - \`{"items": {"$size": 3}}\`
**String Operators**:
- **$regex**: Regular expression - \`{"name": {"$regex": "^John", "$options": "i"}}\`
- **$text**: Text search - \`{"$text": {"$search": "mongodb tutorial"}}\`
### EXAMPLES
**Simple equality**: "Find active users"
→ {"status": "active"}
**By ObjectId**: "Find document by ID"
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
**Range query**: "Find products between $10 and $100"
→ {"price": {"$gte": 10, "$lte": 100}}
**Multiple conditions**: "Find active premium users"
→ {"status": "active", "plan": "premium"}
**OR condition**: "Find active or pending orders"
→ {"$or": [{"status": "active"}, {"status": "pending"}]}
**Array contains**: "Find users with admin role"
→ {"roles": {"$in": ["admin"]}}
**Text search**: "Find posts containing 'mongodb'"
→ {"$text": {"$search": "mongodb"}}
**Date range**: "Find recent posts (last 30 days)"
→ {"createdAt": {"$gte": {"$date": "2024-01-01T00:00:00.000Z"}}}
**Nested object**: "Find users in New York"
→ {"address.city": "New York"}
**Field exists**: "Find users with email addresses"
→ {"email": {"$exists": true, "$ne": null}}
**Complex AND/OR**: "Find active users who are either premium or have high score"
→ {"$and": [{"status": "active"}, {"$or": [{"plan": "premium"}, {"score": {"$gte": 90}}]}]}
**Array matching**: "Find posts with specific tags"
→ {"tags": {"$all": ["javascript", "tutorial"]}}
**Regex pattern**: "Find users with Gmail addresses"
→ {"email": {"$regex": "@gmail\\.com$", "$options": "i"}}
**Size check**: "Find users with exactly 3 hobbies"
→ {"hobbies": {"$size": 3}}
**Null/undefined check**: "Find incomplete profiles"
→ {"$or": [{"profile": null}, {"profile": {"$exists": false}}]}
### SECURITY CONSIDERATIONS
- Avoid directly embedding user input without validation
- Use proper data types (ObjectId for IDs, dates for timestamps)
- Consider query performance and indexing
- Be cautious with regex patterns that could cause performance issues
- Validate that field names exist in your schema
### PERFORMANCE TIPS
- Structure filters to use indexed fields first
- Use specific equality matches when possible
- Avoid complex regex patterns on large collections
- Consider using compound indexes for multi-field queries
- Use $limit in combination with filters for large result sets
### REMEMBER
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. The output must be ready to use directly in MongoDB operations.`,
placeholder: 'Describe the documents you want to find...',
generationType: 'mongodb-filter',
},
},
{
id: 'pipeline',
title: 'Aggregation Pipeline (JSON Array)',
type: 'code',
layout: 'full',
placeholder: '[{"$group": {"_id": "$status", "count": {"$sum": 1}}}]',
condition: { field: 'operation', value: 'execute' },
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert MongoDB aggregation developer. Create MongoDB aggregation pipelines based on the user's request.
### CONTEXT
{context}
### CRITICAL INSTRUCTION
Return ONLY the aggregation pipeline as a valid JSON array. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON array.
### PIPELINE GUIDELINES
1. **Structure**: Always return a JSON array of aggregation stages
2. **Performance**: Order stages efficiently - use $match and $limit early when possible
3. **Security**: Avoid unsafe operations or overly complex expressions
4. **Readability**: Use clear field names and logical stage ordering
5. **Data Types**: Handle ObjectId, dates, numbers, and nested objects correctly
### AGGREGATION STAGES
**Filtering & Matching:**
- $match: Filter documents (use early in pipeline)
- $lookup: Join collections
- $facet: Multi-faceted aggregations
**Grouping & Analysis:**
- $group: Group by fields and calculate aggregates
- $bucket: Group into predefined ranges
- $bucketAuto: Auto-generate buckets
- $count: Count documents
**Transformation:**
- $project: Select/transform fields
- $addFields: Add computed fields
- $set: Set field values
- $unset: Remove fields
- $unwind: Deconstruct arrays
**Sorting & Limiting:**
- $sort: Sort documents
- $limit: Limit results
- $skip: Skip documents
- $sample: Random sample
**Advanced:**
- $graphLookup: Recursive lookups
- $redact: Conditionally reshape documents
- $replaceRoot: Replace document root
### EXAMPLES
**Basic Aggregation**: "Count users by status"
→ [
{"$group": {"_id": "$status", "count": {"$sum": 1}}},
{"$sort": {"count": -1}}
]
**Multi-Stage Analysis**: "Get top 10 customers by total order value"
→ [
{"$match": {"status": "completed"}},
{"$group": {
"_id": "$customerId",
"totalValue": {"$sum": "$amount"},
"orderCount": {"$sum": 1}
}},
{"$sort": {"totalValue": -1}},
{"$limit": 10},
{"$project": {
"customerId": "$_id",
"totalValue": 1,
"orderCount": 1,
"averageOrderValue": {"$divide": ["$totalValue", "$orderCount"]},
"_id": 0
}}
]
**Complex Join**: "Get users with their recent orders and order totals"
→ [
{"$lookup": {
"from": "orders",
"localField": "_id",
"foreignField": "userId",
"as": "orders"
}},
{"$unwind": {
"path": "$orders",
"preserveNullAndEmptyArrays": true
}},
{"$match": {
"$or": [
{"orders.createdAt": {"$gte": new Date(Date.now() - 30*24*60*60*1000)}},
{"orders": {"$exists": false}}
]
}},
{"$group": {
"_id": "$_id",
"name": {"$first": "$name"},
"email": {"$first": "$email"},
"recentOrderCount": {"$sum": {"$cond": [{"$ifNull": ["$orders", false]}, 1, 0]}},
"totalOrderValue": {"$sum": {"$ifNull": ["$orders.amount", 0]}}
}},
{"$sort": {"totalOrderValue": -1}}
]
**Date Analysis**: "Monthly sales trends with growth rates"
→ [
{"$match": {
"createdAt": {"$gte": new Date("2024-01-01")},
"status": "completed"
}},
{"$group": {
"_id": {
"year": {"$year": "$createdAt"},
"month": {"$month": "$createdAt"}
},
"totalSales": {"$sum": "$amount"},
"orderCount": {"$sum": 1}
}},
{"$sort": {"_id.year": 1, "_id.month": 1}},
{"$group": {
"_id": null,
"monthlyData": {"$push": {
"period": "$_id",
"totalSales": "$totalSales",
"orderCount": "$orderCount"
}}
}},
{"$unwind": {
"path": "$monthlyData",
"includeArrayIndex": "index"
}},
{"$project": {
"period": "$monthlyData.period",
"totalSales": "$monthlyData.totalSales",
"orderCount": "$monthlyData.orderCount",
"previousSales": {"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]},
"growthRate": {"$cond": [
{"$and": [
{"$gt": ["$index", 0]},
{"$gt": [{"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}, 0]}
]},
{"$multiply": [
{"$divide": [
{"$subtract": ["$monthlyData.totalSales", {"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}]},
{"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}
]},
100
]},
null
]}
}}
]
**Text Search & Filtering**: "Search products with filters and scoring"
→ [
{"$match": {
"$text": {"$search": "laptop gaming"},
"category": "electronics",
"price": {"$gte": 500, "$lte": 2000}
}},
{"$addFields": {
"searchScore": {"$meta": "textScore"},
"priceScore": {"$divide": [{"$subtract": [2000, "$price"]}, 1500]}
}},
{"$project": {
"name": 1,
"price": 1,
"category": 1,
"rating": 1,
"reviewCount": 1,
"combinedScore": {"$add": [
{"$multiply": ["$searchScore", 0.6]},
{"$multiply": ["$priceScore", 0.2]},
{"$multiply": ["$rating", 0.2]}
]}
}},
{"$sort": {"combinedScore": -1}},
{"$limit": 20}
]
**Geo-spatial Analysis**: "Find nearby stores with inventory"
→ [
{"$geoNear": {
"near": {"type": "Point", "coordinates": [-74.005, 40.7128]},
"distanceField": "distance",
"maxDistance": 10000,
"spherical": true
}},
{"$lookup": {
"from": "inventory",
"localField": "_id",
"foreignField": "storeId",
"as": "inventory"
}},
{"$addFields": {
"inventoryCount": {"$size": "$inventory"},
"hasProduct": {"$in": ["target-product-id", "$inventory.productId"]}
}},
{"$match": {"hasProduct": true}},
{"$project": {
"name": 1,
"address": 1,
"distance": {"$round": ["$distance", 0]},
"inventoryCount": 1
}},
{"$sort": {"distance": 1}},
{"$limit": 5}
]
### PERFORMANCE TIPS
- Place $match as early as possible to reduce document flow
- Use $limit after $sort for top-N queries
- Index fields used in $match, $sort, and $lookup operations
- Use $project to reduce document size in multi-stage pipelines
- Consider $facet for multiple aggregations on same data
### SECURITY CONSIDERATIONS
- Validate input parameters to prevent injection
- Use appropriate read concerns for consistency requirements
- Be cautious with $where and $expr in $match stages
- Limit pipeline complexity to prevent timeout/resource issues
### REMEMBER
Return ONLY the JSON array pipeline - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the aggregation you want to perform...',
generationType: 'mongodb-pipeline',
},
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '100',
condition: { field: 'operation', value: 'query' },
},
{
id: 'sort',
title: 'Sort (JSON)',
type: 'code',
layout: 'full',
placeholder: '{"createdAt": -1}',
condition: { field: 'operation', value: 'query' },
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `Write MongoDB sort criteria as JSON.
### CONTEXT
{context}
### EXAMPLES
Newest first: {"createdAt": -1}
Alphabetical: {"name": 1}
Multiple fields: {"category": 1, "price": -1}
Use 1 for ascending, -1 for descending. Return ONLY valid JSON.`,
placeholder: 'Describe how you want to sort the results...',
generationType: 'mongodb-sort',
},
},
{
id: 'documents',
title: 'Documents (JSON Array)',
type: 'code',
layout: 'full',
placeholder: '[{"name": "John Doe", "email": "john@example.com", "status": "active"}]',
condition: { field: 'operation', value: 'insert' },
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `Write MongoDB documents as JSON array.
### CONTEXT
{context}
### EXAMPLES
Simple user: [{"name": "John Doe", "email": "john@example.com", "active": true}]
With nested data: [{"user": {"name": "Jane", "profile": {"age": 25, "city": "NYC"}}, "status": "active"}]
Multiple docs: [{"name": "User1", "type": "admin"}, {"name": "User2", "type": "user"}]
Return ONLY valid JSON array - no explanations.`,
placeholder: 'Describe the documents you want to insert...',
generationType: 'mongodb-documents',
},
},
{
id: 'filter',
title: 'Filter (JSON)',
type: 'code',
layout: 'full',
placeholder: '{"name": "Alice Test"}',
condition: { field: 'operation', value: 'update' },
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects to target specific documents for UPDATE operations.
### CONTEXT
{context}
### CRITICAL INSTRUCTION
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that will identify which documents to update.
### UPDATE FILTER GUIDELINES
1. **Precision**: Use specific criteria to target exact documents you want to update
2. **Safety**: Avoid broad filters that might update unintended documents
3. **Uniqueness**: When possible, use unique identifiers like _id or email
4. **Verification**: Consider what happens if multiple documents match your filter
### MONGODB QUERY OPERATORS
**Comparison Operators**:
- **$eq**: Equals - \`{"status": {"$eq": "active"}}\` or \`{"status": "active"}\`
- **$ne**: Not equals - \`{"status": {"$ne": "inactive"}}\`
- **$gt**: Greater than - \`{"age": {"$gt": 18}}\`
- **$gte**: Greater than or equal - \`{"price": {"$gte": 100}}\`
- **$lt**: Less than - \`{"score": {"$lt": 90}}\`
- **$lte**: Less than or equal - \`{"rating": {"$lte": 5}}\`
- **$in**: In array - \`{"category": {"$in": ["tech", "science"]}}\`
- **$nin**: Not in array - \`{"status": {"$nin": ["deleted", "banned"]}}\`
**Logical Operators**:
- **$and**: AND condition - \`{"$and": [{"age": {"$gte": 18}}, {"status": "active"}]}\`
- **$or**: OR condition - \`{"$or": [{"status": "active"}, {"status": "pending"}]}\`
- **$not**: NOT condition - \`{"age": {"$not": {"$lt": 18}}}\`
**Element Operators**:
- **$exists**: Field exists - \`{"email": {"$exists": true}}\`
- **$type**: Field type - \`{"count": {"$type": "number"}}\`
### UPDATE FILTER EXAMPLES
**By unique ID**: "Update specific document by ID"
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
**By unique email**: "Update user with specific email"
→ {"email": "john.doe@example.com"}
**By username**: "Update user account by username"
→ {"username": "johndoe"}
**Multiple specific criteria**: "Update active users in sales department"
→ {"status": "active", "department": "sales"}
**Conditional update**: "Update expired premium accounts"
→ {"plan": "premium", "expiryDate": {"$lt": {"$date": "2024-01-01T00:00:00.000Z"}}}
**Status-based update**: "Update all pending orders"
→ {"status": "pending"}
**Range-based update**: "Update products with low stock"
→ {"stock": {"$lt": 10}, "active": true}
**Complex condition**: "Update users who haven't logged in recently"
→ {"$and": [{"status": "active"}, {"lastLogin": {"$lt": {"$date": "2023-12-01T00:00:00.000Z"}}}]}
### SAFETY CONSIDERATIONS FOR UPDATES
- **Always test your filter first** with a find() operation to see what documents match
- **Use unique identifiers** when updating single documents (_id, email, username)
- **Be specific** - avoid filters that might match more documents than intended
- **Consider using $and** to combine multiple conditions for precision
- **Validate field names** exist in your schema before updating
### REMEMBER
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. This filter will determine which documents get updated, so be precise and careful.`,
placeholder: 'Describe which documents to update...',
generationType: 'mongodb-filter',
},
},
{
id: 'update',
title: 'Update (JSON)',
type: 'code',
layout: 'full',
placeholder: '{"$set": {"name": "Jane Doe", "email": "jane@example.com"}}',
condition: { field: 'operation', value: 'update' },
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert MongoDB developer. Generate ONLY the raw JSON update operation based on the user's request.
The output MUST be a single, valid JSON object representing MongoDB update operators.
### CONTEXT
{context}
### SAFETY CONSIDERATIONS
- ALWAYS use atomic operators ($set, $unset, $inc, etc.) - never provide raw field assignments
- For destructive operations like $unset or array modifications, ensure the request is intentional
- Consider data types when setting values (strings, numbers, booleans, dates, ObjectIds)
- Use $addToSet instead of $push to prevent duplicate array elements when appropriate
- Validate that increment operations use numeric values
### MONGODB UPDATE OPERATORS & EXAMPLES
#### Field Update Operators:
- $set (update/create fields): {"$set": {"name": "John Doe", "email": "john@example.com", "lastLogin": new Date()}}
- $unset (remove fields): {"$unset": {"temporaryField": "", "deprecatedData": ""}}
- $inc (increment numbers): {"$inc": {"views": 1, "score": -5, "balance": 100.50}}
- $mul (multiply values): {"$mul": {"price": 0.9, "quantity": 2}}
- $min (update if smaller): {"$min": {"lowestScore": 85}}
- $max (update if larger): {"$max": {"highestScore": 95}}
- $currentDate (set current date): {"$currentDate": {"lastModified": true, "timestamp": {"$type": "date"}}}
- $rename (rename fields): {"$rename": {"old_name": "new_name", "temp_field": "permanent_field"}}
#### Array Update Operators:
- $push (add element): {"$push": {"tags": "new-tag", "comments": {"user": "john", "text": "Great post!"}}}
- $push with $each (add multiple): {"$push": {"tags": {"$each": ["tag1", "tag2", "tag3"]}}}
- $push with $position: {"$push": {"items": {"$each": ["new-item"], "$position": 0}}}
- $push with $slice: {"$push": {"recent_activity": {"$each": [{"action": "login"}], "$slice": -10}}}
- $addToSet (add unique): {"$addToSet": {"tags": "unique-tag", "categories": {"$each": ["cat1", "cat2"]}}}
- $pull (remove matching): {"$pull": {"tags": "unwanted-tag", "items": {"status": "deleted"}}}
- $pullAll (remove multiple): {"$pullAll": {"tags": ["tag1", "tag2"], "numbers": [1, 3, 5]}}
- $pop (remove first/last): {"$pop": {"queue": -1, "stack": 1}}
#### Array Element Updates:
- Positional $ operator: {"$set": {"comments.$.approved": true, "items.$.quantity": 5}}
- Array filters $[]: {"$set": {"items.$[elem].status": "active"}}, arrayFilters: [{"elem.category": "electronics"}]
- Multiple positional $[]: {"$inc": {"items.$[item].reviews.$[review].helpful": 1}}
#### Complex Combinations:
- Multiple operations: {"$set": {"name": "Updated Name", "status": "active"}, "$inc": {"version": 1}, "$push": {"history": {"action": "updated", "date": new Date()}}}
- Nested field updates: {"$set": {"profile.settings.notifications": true, "profile.lastSeen": new Date()}}
- Conditional updates with $cond in aggregation: {"$set": {"discount": {"$cond": [{"$gte": ["$orderAmount", 100]}, 0.1, 0]}}}
#### Data Type Examples:
- String: {"$set": {"name": "John Doe", "status": "active"}}
- Number: {"$set": {"age": 25, "score": 87.5}, "$inc": {"points": 100}}
- Boolean: {"$set": {"isActive": true, "verified": false}}
- Date: {"$set": {"createdAt": new Date("2024-01-01"), "updatedAt": new Date()}}
- ObjectId: {"$set": {"userId": ObjectId("507f1f77bcf86cd799439011")}}
- Array: {"$set": {"tags": ["mongodb", "database", "nosql"]}}
- Object: {"$set": {"metadata": {"source": "api", "version": "1.2", "processed": true}}}
### IMPORTANT FORMATTING RULES:
1. Return ONLY valid JSON - no explanations, comments, or markdown formatting
2. Use proper MongoDB update operator syntax
3. Ensure all string values are properly quoted
4. Use appropriate data types (new Date() for dates, ObjectId() for IDs)
5. For array operations, consider whether $addToSet (unique) or $push (allows duplicates) is appropriate
6. When incrementing, ensure the value is numeric
7. Structure complex nested updates clearly
8. Always validate that field paths exist in your data model
### REFERENCE VARIABLES:
You have access to workflow context variables:
- Input parameters: Use <paramName> syntax (e.g., <userId>, <newStatus>)
- Environment variables: Use {{ENV_VAR}} syntax (e.g., {{DEFAULT_STATUS}})
- Previous block outputs: Use <block.previousBlock.output.field> syntax
Generate the MongoDB update operation that safely and accurately fulfills the user's request.`,
placeholder: 'Describe what you want to update...',
generationType: 'mongodb-update',
},
},
{
id: 'upsert',
title: 'Upsert',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'update' },
},
{
id: 'multi',
title: 'Update Multiple',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'update' },
},
{
id: 'filter',
title: 'Filter (JSON)',
type: 'code',
layout: 'full',
placeholder: '{"status": "inactive"}',
condition: { field: 'operation', value: 'delete' },
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects to target specific documents for DELETION operations.
### CONTEXT
{context}
### CRITICAL INSTRUCTION
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that will identify which documents to delete.
### ⚠️ DELETION SAFETY WARNING ⚠️
DELETIONS ARE PERMANENT! This filter will determine which documents are permanently removed from the database. Be extremely careful and specific with your criteria.
### DELETE FILTER GUIDELINES
1. **EXTREME PRECISION**: Use the most specific criteria possible to target exact documents
2. **UNIQUE IDENTIFIERS**: Prefer using unique fields like _id, email, or username
3. **TEST FIRST**: Always test with find() before deleting to verify what matches
4. **BACKUP**: Consider backing up data before bulk deletions
5. **VALIDATION**: Double-check that your filter criteria are correct
### MONGODB QUERY OPERATORS
**Comparison Operators**:
- **$eq**: Equals - \`{"status": {"$eq": "inactive"}}\` or \`{"status": "inactive"}\`
- **$ne**: Not equals - \`{"status": {"$ne": "active"}}\`
- **$gt**: Greater than - \`{"age": {"$gt": 65}}\`
- **$gte**: Greater than or equal - \`{"createdAt": {"$gte": {"$date": "2024-01-01T00:00:00.000Z"}}}\`
- **$lt**: Less than - \`{"lastLogin": {"$lt": {"$date": "2023-01-01T00:00:00.000Z"}}}\`
- **$lte**: Less than or equal - \`{"score": {"$lte": 0}}\`
- **$in**: In array - \`{"status": {"$in": ["deleted", "banned", "inactive"]}}\`
- **$nin**: Not in array - \`{"type": {"$nin": ["admin", "moderator"]}}\`
**Logical Operators**:
- **$and**: AND condition - \`{"$and": [{"status": "inactive"}, {"lastLogin": {"$lt": {"$date": "2023-01-01T00:00:00.000Z"}}}]}\`
- **$or**: OR condition - \`{"$or": [{"status": "deleted"}, {"status": "banned"}]}\`
- **$not**: NOT condition - \`{"status": {"$not": {"$eq": "active"}}}\`
**Element Operators**:
- **$exists**: Field exists - \`{"deletedAt": {"$exists": true}}\`
- **$type**: Field type - \`{"tempData": {"$type": "null"}}\`
### DELETE FILTER EXAMPLES
**By unique ID**: "Delete specific document by ID"
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
**By unique email**: "Delete specific user account"
→ {"email": "user.to.delete@example.com"}
**By inactive status**: "Delete inactive user accounts"
→ {"status": "inactive"}
**By deletion flag**: "Delete documents marked for deletion"
→ {"markedForDeletion": true}
**Old temporary data**: "Delete temporary data older than 30 days"
→ {"type": "temp", "createdAt": {"$lt": {"$date": "2023-12-01T00:00:00.000Z"}}}
**Expired sessions**: "Delete expired user sessions"
→ {"type": "session", "expiresAt": {"$lt": {"$date": "2024-01-01T00:00:00.000Z"}}}
**Banned users**: "Delete banned user accounts"
→ {"status": "banned", "bannedAt": {"$exists": true}}
**Multiple criteria**: "Delete inactive accounts with no recent activity"
→ {"$and": [{"status": "inactive"}, {"lastLogin": {"$lt": {"$date": "2023-06-01T00:00:00.000Z"}}}, {"verified": false}]}
**By category**: "Delete draft posts older than 90 days"
→ {"status": "draft", "createdAt": {"$lt": {"$date": "2023-10-01T00:00:00.000Z"}}}
### EXTREME SAFETY CONSIDERATIONS FOR DELETIONS
- **⚠️ ALWAYS TEST YOUR FILTER FIRST** with db.collection.find(filter) to see exactly what will be deleted
- **Use the most specific criteria possible** - prefer unique identifiers
- **Consider soft deletion** (marking as deleted) instead of hard deletion
- **Backup important data** before performing bulk deletions
- **Avoid broad filters** that might delete more than intended
- **Validate field names** exist in your schema
- **Document your deletion criteria** for audit purposes
- **Consider the impact** on related data and foreign keys
### REMEMBER
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. This filter will PERMANENTLY DELETE documents, so be extremely careful and precise!`,
placeholder: 'Describe which documents to delete...',
generationType: 'mongodb-filter',
},
},
{
id: 'multi',
title: 'Delete Multiple',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'delete' },
},
],
tools: {
access: [
'mongodb_query',
'mongodb_insert',
'mongodb_update',
'mongodb_delete',
'mongodb_execute',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'query':
return 'mongodb_query'
case 'insert':
return 'mongodb_insert'
case 'update':
return 'mongodb_update'
case 'delete':
return 'mongodb_delete'
case 'execute':
return 'mongodb_execute'
default:
throw new Error(`Invalid MongoDB operation: ${params.operation}`)
}
},
params: (params) => {
const { operation, documents, ...rest } = params
let parsedDocuments
if (documents && typeof documents === 'string' && documents.trim()) {
try {
parsedDocuments = JSON.parse(documents)
} catch (parseError) {
const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
throw new Error(
`Invalid JSON documents format: ${errorMsg}. Please check your JSON syntax.`
)
}
} else if (documents && typeof documents === 'object') {
parsedDocuments = documents
}
const connectionConfig = {
host: rest.host,
port: typeof rest.port === 'string' ? Number.parseInt(rest.port, 10) : rest.port || 27017,
database: rest.database,
username: rest.username,
password: rest.password,
authSource: rest.authSource,
ssl: rest.ssl || 'preferred',
}
const result: any = { ...connectionConfig }
if (rest.collection) result.collection = rest.collection
if (rest.query) {
result.query = typeof rest.query === 'string' ? rest.query : JSON.stringify(rest.query)
}
if (rest.limit && rest.limit !== '') {
result.limit =
typeof rest.limit === 'string' ? Number.parseInt(rest.limit, 10) : rest.limit
} else {
result.limit = 100 // Default to 100 if not provided
}
if (rest.sort) {
result.sort = typeof rest.sort === 'string' ? rest.sort : JSON.stringify(rest.sort)
}
if (rest.filter) {
result.filter =
typeof rest.filter === 'string' ? rest.filter : JSON.stringify(rest.filter)
}
if (rest.update) {
result.update =
typeof rest.update === 'string' ? rest.update : JSON.stringify(rest.update)
}
if (rest.pipeline) {
result.pipeline =
typeof rest.pipeline === 'string' ? rest.pipeline : JSON.stringify(rest.pipeline)
}
if (rest.upsert) result.upsert = rest.upsert === 'true' || rest.upsert === true
if (rest.multi) result.multi = rest.multi === 'true' || rest.multi === true
if (parsedDocuments !== undefined) result.documents = parsedDocuments
return result
},
},
},
inputs: {
operation: { type: 'string', description: 'Database operation to perform' },
host: { type: 'string', description: 'MongoDB host' },
port: { type: 'string', description: 'MongoDB port' },
database: { type: 'string', description: 'Database name' },
username: { type: 'string', description: 'MongoDB username' },
password: { type: 'string', description: 'MongoDB password' },
authSource: { type: 'string', description: 'Authentication database' },
ssl: { type: 'string', description: 'SSL mode' },
collection: { type: 'string', description: 'Collection name' },
query: { type: 'string', description: 'Query filter as JSON string' },
limit: { type: 'number', description: 'Limit number of documents' },
sort: { type: 'string', description: 'Sort criteria as JSON string' },
documents: { type: 'json', description: 'Documents to insert' },
filter: { type: 'string', description: 'Filter criteria as JSON string' },
update: { type: 'string', description: 'Update operations as JSON string' },
pipeline: { type: 'string', description: 'Aggregation pipeline as JSON string' },
upsert: { type: 'boolean', description: 'Create document if not found' },
multi: { type: 'boolean', description: 'Operate on multiple documents' },
},
outputs: {
message: {
type: 'string',
description: 'Success or error message describing the operation outcome',
},
documents: {
type: 'array',
description: 'Array of documents returned from the operation',
},
documentCount: {
type: 'number',
description: 'Number of documents affected by the operation',
},
insertedId: {
type: 'string',
description: 'ID of the inserted document (single insert)',
},
insertedIds: {
type: 'array',
description: 'Array of IDs for inserted documents (multiple insert)',
},
modifiedCount: {
type: 'number',
description: 'Number of documents modified (update operations)',
},
deletedCount: {
type: 'number',
description: 'Number of documents deleted (delete operations)',
},
matchedCount: {
type: 'number',
description: 'Number of documents matched (update operations)',
},
},
}

View File

@@ -131,14 +131,7 @@ export const TranslateBlock: BlockConfig = {
},
],
tools: {
access: [
'openai_chat',
'anthropic_chat',
'google_chat',
'xai_chat',
'deepseek_chat',
'deepseek_reasoner',
],
access: ['openai_chat', 'anthropic_chat', 'google_chat'],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o'

View File

@@ -40,6 +40,7 @@ import { MicrosoftExcelBlock } from '@/blocks/blocks/microsoft_excel'
import { MicrosoftPlannerBlock } from '@/blocks/blocks/microsoft_planner'
import { MicrosoftTeamsBlock } from '@/blocks/blocks/microsoft_teams'
import { MistralParseBlock } from '@/blocks/blocks/mistral_parse'
import { MongoDBBlock } from '@/blocks/blocks/mongodb'
import { MySQLBlock } from '@/blocks/blocks/mysql'
import { NotionBlock } from '@/blocks/blocks/notion'
import { OneDriveBlock } from '@/blocks/blocks/onedrive'
@@ -116,6 +117,7 @@ export const registry: Record<string, BlockConfig> = {
microsoft_planner: MicrosoftPlannerBlock,
microsoft_teams: MicrosoftTeamsBlock,
mistral_parse: MistralParseBlock,
mongodb: MongoDBBlock,
mysql: MySQLBlock,
notion: NotionBlock,
openai: OpenAIBlock,

View File

@@ -1,15 +1,12 @@
import type { JSX, SVGProps } from 'react'
import type { ToolResponse } from '@/tools/types'
// Basic types
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
export type ParamType = 'string' | 'number' | 'boolean' | 'json'
export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'array' | 'any'
// Block classification
export type BlockCategory = 'blocks' | 'tools' | 'triggers'
// Valid generation types for AI assistance
export type GenerationType =
| 'javascript-function-body'
| 'typescript-function-body'
@@ -19,8 +16,12 @@ export type GenerationType =
| 'custom-tool-schema'
| 'sql-query'
| 'postgrest'
| 'mongodb-filter'
| 'mongodb-pipeline'
| 'mongodb-sort'
| 'mongodb-documents'
| 'mongodb-update'
// SubBlock types
export type SubBlockType =
| 'short-input' // Single line input
| 'long-input' // Multi-line input
@@ -51,13 +52,10 @@ export type SubBlockType =
| 'response-format' // Response structure format
| 'file-upload' // File uploader
// Component width setting
export type SubBlockLayout = 'full' | 'half'
// Tool result extraction
export type ExtractToolOutput<T> = T extends ToolResponse ? T['output'] : never
// Convert tool output to types
export type ToolOutputToValueType<T> = T extends Record<string, any>
? {
[K in keyof T]: T[K] extends string
@@ -72,17 +70,14 @@ export type ToolOutputToValueType<T> = T extends Record<string, any>
}
: never
// Block output definition
export type BlockOutput =
| PrimitiveValueType
| { [key: string]: PrimitiveValueType | Record<string, any> }
// Output field definition with optional description
export type OutputFieldDefinition =
| PrimitiveValueType
| { type: PrimitiveValueType; description?: string }
// Parameter validation rules
export interface ParamConfig {
type: ParamType
description?: string
@@ -100,7 +95,6 @@ export interface ParamConfig {
}
}
// SubBlock configuration
export interface SubBlockConfig {
id: string
title?: string
@@ -177,7 +171,6 @@ export interface SubBlockConfig {
dependsOn?: string[]
}
// Main block definition
export interface BlockConfig<T extends ToolResponse = ToolResponse> {
type: string
name: string
@@ -209,7 +202,6 @@ export interface BlockConfig<T extends ToolResponse = ToolResponse> {
}
}
// Output configuration rules
export interface OutputConfig {
type: BlockOutput
}

View File

@@ -3452,3 +3452,106 @@ export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function MongoDBIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='currentColor'
d='M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#45A538'
d='M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#46A037'
d='M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#409433'
d='M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4FAA41'
d='M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4AA73C'
d='M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#57AE47'
d='M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#60B24F'
d='M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#A9AA88'
d='M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#B6B598'
d='M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#C2C1A7'
d='M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#CECDB7'
d='M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#DBDAC7'
d='M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#EBE9DC'
d='M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#CECDB7'
d='M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
fill='#4FAA41'
d='M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z'
/>
</svg>
)
}

View File

@@ -0,0 +1,116 @@
import type { MongoDBDeleteParams, MongoDBResponse } from '@/tools/mongodb/types'
import type { ToolConfig } from '@/tools/types'
export const deleteTool: ToolConfig<MongoDBDeleteParams, MongoDBResponse> = {
id: 'mongodb_delete',
name: 'MongoDB Delete',
description: 'Delete documents from MongoDB collection',
version: '1.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'MongoDB server hostname or IP address',
},
port: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'MongoDB server port (default: 27017)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name to connect to',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB username',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB password',
},
authSource: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication database',
},
ssl: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'SSL connection mode (disabled, required, preferred)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Collection name to delete from',
},
filter: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Filter criteria as JSON string',
},
multi: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Delete multiple documents',
},
},
request: {
url: '/api/tools/mongodb/delete',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl || 'preferred',
collection: params.collection,
filter: params.filter,
multi: params.multi || false,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'MongoDB delete failed')
}
return {
success: true,
output: {
message: data.message || 'Documents deleted successfully',
deletedCount: data.deletedCount || 0,
documentCount: data.documentCount || 0,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
deletedCount: { type: 'number', description: 'Number of documents deleted' },
documentCount: { type: 'number', description: 'Total number of documents affected' },
},
}

View File

@@ -0,0 +1,109 @@
import type { MongoDBExecuteParams, MongoDBResponse } from '@/tools/mongodb/types'
import type { ToolConfig } from '@/tools/types'
export const executeTool: ToolConfig<MongoDBExecuteParams, MongoDBResponse> = {
id: 'mongodb_execute',
name: 'MongoDB Execute',
description: 'Execute MongoDB aggregation pipeline',
version: '1.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'MongoDB server hostname or IP address',
},
port: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'MongoDB server port (default: 27017)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name to connect to',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB username',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB password',
},
authSource: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication database',
},
ssl: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'SSL connection mode (disabled, required, preferred)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Collection name to execute pipeline on',
},
pipeline: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Aggregation pipeline as JSON string',
},
},
request: {
url: '/api/tools/mongodb/execute',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl || 'preferred',
collection: params.collection,
pipeline: params.pipeline,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'MongoDB aggregation failed')
}
return {
success: true,
output: {
message: data.message || 'Aggregation executed successfully',
documents: data.documents || [],
documentCount: data.documentCount || 0,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
documents: { type: 'array', description: 'Array of documents returned from aggregation' },
documentCount: { type: 'number', description: 'Number of documents returned' },
},
}

View File

@@ -0,0 +1,6 @@
export { deleteTool } from './delete'
export { executeTool } from './execute'
export { insertTool } from './insert'
export { queryTool } from './query'
export * from './types'
export { updateTool } from './update'

View File

@@ -0,0 +1,111 @@
import type { MongoDBInsertParams, MongoDBResponse } from '@/tools/mongodb/types'
import type { ToolConfig } from '@/tools/types'
export const insertTool: ToolConfig<MongoDBInsertParams, MongoDBResponse> = {
id: 'mongodb_insert',
name: 'MongoDB Insert',
description: 'Insert documents into MongoDB collection',
version: '1.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'MongoDB server hostname or IP address',
},
port: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'MongoDB server port (default: 27017)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name to connect to',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB username',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB password',
},
authSource: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication database',
},
ssl: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'SSL connection mode (disabled, required, preferred)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Collection name to insert into',
},
documents: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Array of documents to insert',
},
},
request: {
url: '/api/tools/mongodb/insert',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl || 'preferred',
collection: params.collection,
documents: params.documents,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'MongoDB insert failed')
}
return {
success: true,
output: {
message: data.message || 'Documents inserted successfully',
documentCount: data.documentCount || 0,
insertedId: data.insertedId,
insertedIds: data.insertedIds,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
documentCount: { type: 'number', description: 'Number of documents inserted' },
insertedId: { type: 'string', description: 'ID of inserted document (single insert)' },
insertedIds: { type: 'array', description: 'Array of inserted document IDs (multiple insert)' },
},
}

View File

@@ -0,0 +1,123 @@
import type { MongoDBQueryParams, MongoDBResponse } from '@/tools/mongodb/types'
import type { ToolConfig } from '@/tools/types'
export const queryTool: ToolConfig<MongoDBQueryParams, MongoDBResponse> = {
id: 'mongodb_query',
name: 'MongoDB Query',
description: 'Execute find operation on MongoDB collection',
version: '1.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'MongoDB server hostname or IP address',
},
port: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'MongoDB server port (default: 27017)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name to connect to',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB username',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB password',
},
authSource: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication database',
},
ssl: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'SSL connection mode (disabled, required, preferred)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Collection name to query',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'MongoDB query filter as JSON string',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of documents to return',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort criteria as JSON string',
},
},
request: {
url: '/api/tools/mongodb/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl || 'preferred',
collection: params.collection,
query: params.query,
limit: params.limit,
sort: params.sort,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'MongoDB query failed')
}
return {
success: true,
output: {
message: data.message || 'Query executed successfully',
documents: data.documents || [],
documentCount: data.documentCount || 0,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
documents: { type: 'array', description: 'Array of documents returned from the query' },
documentCount: { type: 'number', description: 'Number of documents returned' },
},
}

View File

@@ -0,0 +1,63 @@
import type { ToolResponse } from '@/tools/types'
export interface MongoDBConnectionConfig {
host: string
port: number
database: string
username?: string
password?: string
authSource?: string
ssl?: 'disabled' | 'required' | 'preferred'
}
export interface MongoDBQueryParams extends MongoDBConnectionConfig {
collection: string
query?: string
limit?: number
sort?: string
}
export interface MongoDBInsertParams extends MongoDBConnectionConfig {
collection: string
documents: unknown[]
}
export interface MongoDBUpdateParams extends MongoDBConnectionConfig {
collection: string
filter: string
update: string
upsert?: boolean
multi?: boolean
}
export interface MongoDBDeleteParams extends MongoDBConnectionConfig {
collection: string
filter: string
multi?: boolean
}
export interface MongoDBExecuteParams extends MongoDBConnectionConfig {
collection: string
pipeline: string
}
export interface MongoDBBaseResponse extends ToolResponse {
output: {
message: string
documents?: unknown[]
documentCount: number
insertedId?: string
insertedIds?: string[]
modifiedCount?: number
deletedCount?: number
matchedCount?: number
}
error?: string
}
export interface MongoDBQueryResponse extends MongoDBBaseResponse {}
export interface MongoDBInsertResponse extends MongoDBBaseResponse {}
export interface MongoDBUpdateResponse extends MongoDBBaseResponse {}
export interface MongoDBDeleteResponse extends MongoDBBaseResponse {}
export interface MongoDBExecuteResponse extends MongoDBBaseResponse {}
export interface MongoDBResponse extends MongoDBBaseResponse {}

View File

@@ -0,0 +1,134 @@
import type { MongoDBResponse, MongoDBUpdateParams } from '@/tools/mongodb/types'
import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig<MongoDBUpdateParams, MongoDBResponse> = {
id: 'mongodb_update',
name: 'MongoDB Update',
description: 'Update documents in MongoDB collection',
version: '1.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'MongoDB server hostname or IP address',
},
port: {
type: 'number',
required: true,
visibility: 'user-only',
description: 'MongoDB server port (default: 27017)',
},
database: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Database name to connect to',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB username',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'MongoDB password',
},
authSource: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication database',
},
ssl: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'SSL connection mode (disabled, required, preferred)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Collection name to update',
},
filter: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Filter criteria as JSON string',
},
update: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Update operations as JSON string',
},
upsert: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Create document if not found',
},
multi: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Update multiple documents',
},
},
request: {
url: '/api/tools/mongodb/update',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
host: params.host,
port: params.port,
database: params.database,
username: params.username,
password: params.password,
authSource: params.authSource,
ssl: params.ssl || 'preferred',
collection: params.collection,
filter: params.filter,
update: params.update,
upsert: params.upsert || false,
multi: params.multi || false,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'MongoDB update failed')
}
return {
success: true,
output: {
message: data.message || 'Documents updated successfully',
matchedCount: data.matchedCount || 0,
modifiedCount: data.modifiedCount || 0,
documentCount: data.documentCount || 0,
insertedId: data.insertedId,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
matchedCount: { type: 'number', description: 'Number of documents matched by filter' },
modifiedCount: { type: 'number', description: 'Number of documents modified' },
documentCount: { type: 'number', description: 'Total number of documents affected' },
insertedId: { type: 'string', description: 'ID of inserted document (if upsert)' },
},
}

View File

@@ -91,6 +91,13 @@ import {
microsoftTeamsWriteChatTool,
} from '@/tools/microsoft_teams'
import { mistralParserTool } from '@/tools/mistral'
import {
deleteTool as mongodbDeleteTool,
executeTool as mongodbExecuteTool,
insertTool as mongodbInsertTool,
queryTool as mongodbQueryTool,
updateTool as mongodbUpdateTool,
} from '@/tools/mongodb'
import {
deleteTool as mysqlDeleteTool,
executeTool as mysqlExecuteTool,
@@ -242,6 +249,11 @@ export const tools: Record<string, ToolConfig> = {
postgresql_update: postgresUpdateTool,
postgresql_delete: postgresDeleteTool,
postgresql_execute: postgresExecuteTool,
mongodb_query: mongodbQueryTool,
mongodb_insert: mongodbInsertTool,
mongodb_update: mongodbUpdateTool,
mongodb_delete: mongodbDeleteTool,
mongodb_execute: mongodbExecuteTool,
mysql_query: mysqlQueryTool,
mysql_insert: mysqlInsertTool,
mysql_update: mysqlUpdateTool,

View File

@@ -8,6 +8,7 @@
"@t3-oss/env-nextjs": "0.13.4",
"@vercel/analytics": "1.5.0",
"geist": "^1.4.2",
"mongodb": "6.19.0",
"react-colorful": "5.6.1",
"remark-gfm": "4.0.1",
"socket.io-client": "4.8.1",
@@ -672,6 +673,8 @@
"@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=="],
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.3.0", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ=="],
"@napi-rs/canvas": ["@napi-rs/canvas@0.1.78", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.78", "@napi-rs/canvas-darwin-arm64": "0.1.78", "@napi-rs/canvas-darwin-x64": "0.1.78", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.78", "@napi-rs/canvas-linux-arm64-gnu": "0.1.78", "@napi-rs/canvas-linux-arm64-musl": "0.1.78", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.78", "@napi-rs/canvas-linux-x64-gnu": "0.1.78", "@napi-rs/canvas-linux-x64-musl": "0.1.78", "@napi-rs/canvas-win32-x64-msvc": "0.1.78" } }, "sha512-YaBHJvT+T1DoP16puvWM6w46Lq3VhwKIJ8th5m1iEJyGh7mibk5dT7flBvMQ1EH1LYmMzXJ+OUhu+8wQ9I6u7g=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.78", "", { "os": "android", "cpu": "arm64" }, "sha512-N1ikxztjrRmh8xxlG5kYm1RuNr8ZW1EINEDQsLhhuy7t0pWI/e7SH91uFVLZKCMDyjel1tyWV93b5fdCAi7ggw=="],
@@ -1476,10 +1479,14 @@
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
"@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
"@types/webpack": ["@types/webpack@5.28.5", "", { "dependencies": { "@types/node": "*", "tapable": "^2.2.0", "webpack": "^5" } }, "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw=="],
"@types/webxr": ["@types/webxr@0.5.23", "", {}, "sha512-GPe4AsfOSpqWd3xA/0gwoKod13ChcfV67trvxaW2krUbgb9gxQjnCx8zGshzMl8LSHZlNH5gQ8LNScsDuc7nGQ=="],
"@types/whatwg-url": ["@types/whatwg-url@11.0.5", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ=="],
"@types/xlsx": ["@types/xlsx@0.0.36", "", { "dependencies": { "xlsx": "*" } }, "sha512-mvfrKiKKMErQzLMF8ElYEH21qxWCZtN59pHhWGmWCWFJStYdMWjkDSAy6mGowFxHXaXZWe5/TW7pBUiWclIVOw=="],
"@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.0", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-sOx1PKSuFwnIl7z4RN0Ls7N9AQawmR9r66eI5rFCzLDIs8HTIYrIpH9QjYWoX0lkgGrkLxXhi4QnK7MizPRrIg=="],
@@ -1646,6 +1653,8 @@
"browserslist": ["browserslist@4.25.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg=="],
"bson": ["bson@6.10.4", "", {}, "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
@@ -2240,7 +2249,7 @@
"is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
@@ -2430,6 +2439,8 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
@@ -2536,6 +2547,10 @@
"module-punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"mongodb": ["mongodb@6.19.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", "snappy": "^7.3.2", "socks": "^2.7.1" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw=="],
"mongodb-connection-string-url": ["mongodb-connection-string-url@3.0.2", "", { "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" } }, "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA=="],
"motion-dom": ["motion-dom@12.23.12", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw=="],
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
@@ -2988,6 +3003,8 @@
"spamc": ["spamc@0.0.5", "", {}, "sha512-jYXItuZuiWZyG9fIdvgTUbp2MNRuyhuSwvvhhpPJd4JK/9oSZxkD7zAj53GJtowSlXwCJzLg6sCKAoE9wXsKgg=="],
"sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
@@ -3754,6 +3771,8 @@
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"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=="],
@@ -3764,8 +3783,6 @@
"fumadocs-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"gaxios/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=="],
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],

View File

@@ -36,6 +36,7 @@
"@t3-oss/env-nextjs": "0.13.4",
"@vercel/analytics": "1.5.0",
"geist": "^1.4.2",
"mongodb": "6.19.0",
"react-colorful": "5.6.1",
"remark-gfm": "4.0.1",
"socket.io-client": "4.8.1"