Skip to main content
Glama
file-ingestion.controller.ts6.1 kB
import { Controller, Post, Req, BadRequestException, Body, ForbiddenException, } from '@nestjs/common'; import { FileIngestionService } from '../services/file-ingestion.service.js'; import { FileValidationService, getGuardValue } from '@snakagent/core'; import { MultipartFile } from '@fastify/multipart'; import { FastifyRequest } from 'fastify'; import { ConfigurationService } from '../../config/configuration.js'; import { ErrorHandler } from '../utils/error-handler.js'; import { ControllerHelpers } from '../utils/controller-helpers.js'; import { AgentStorage } from '../agents.storage.js'; import { logger } from '@snakagent/core'; import { randomUUID } from 'crypto'; interface MultipartField { type: 'field'; fieldname: string; value: unknown; } interface MultipartRequest { isMultipart: () => boolean; parts: () => AsyncIterableIterator<MultipartFile | MultipartField>; } @Controller('files') export class FileIngestionController { constructor( private readonly service: FileIngestionService, private readonly config: ConfigurationService, private readonly fileValidationService: FileValidationService, private readonly agentFactory: AgentStorage ) {} @Post('upload') async upload(@Req() request: FastifyRequest): Promise<{ jobId: string }> { return ErrorHandler.handleControllerError(async () => { logger.info('files.upload called'); const req = request as unknown as MultipartRequest; if (!req.isMultipart || !req.isMultipart()) { logger.error('Multipart request expected'); throw new BadRequestException('Multipart request expected'); } const userId = ControllerHelpers.getUserId(request); let agentId = ''; let fileBuffer: Buffer | undefined; let fileName = ''; let fileSize = 0; const parts = req.parts(); if (!parts) { logger.error('Unable to access multipart parts from request'); throw new BadRequestException( 'Invalid multipart request - parts not accessible' ); } let partCount = 0; for await (const part of parts) { partCount++; logger.debug(`Processing part ${partCount}, type: ${part.type}`); if (part.type === 'field' && part.fieldname === 'agentId') { agentId = String(part.value); } else if (part.type === 'file') { let size = 0; const chunks: Buffer[] = []; let chunkCount = 0; for await (const chunk of part.file) { chunkCount++; size += chunk.length; logger.debug( `Chunk ${chunkCount}: ${chunk.length} bytes (total: ${size} bytes)` ); const maxRagSize = getGuardValue('rag.max_size'); if (size > maxRagSize) { logger.error(`File size ${size} exceeds limit ${maxRagSize}`); part.file.destroy(); throw new ForbiddenException('File size exceeds limit'); } chunks.push(chunk); } fileBuffer = Buffer.concat(chunks); fileName = part.filename; fileSize = size; } } if (!fileBuffer) { logger.error('No file found in request'); throw new BadRequestException('No file found in request'); } if (!agentId || agentId.trim() === '') { logger.error('No agentId provided in request'); throw new BadRequestException('agentId is required'); } // Verify agent ownership before proceeding await ControllerHelpers.verifyAgentOwnership( this.agentFactory, agentId, userId ); const fileId = randomUUID(); // Secure file validation using centralized service const validationResult = await this.fileValidationService.validateFile( fileBuffer, fileName ); if (!validationResult.isValid) { logger.error( `File validation failed for ${fileName}: ${validationResult.error}`, { detectedMimeType: validationResult.detectedMimeType, declaredMimeType: validationResult.declaredMimeType, } ); throw new BadRequestException(validationResult.error); } const mimeType = validationResult.validatedMimeType; logger.info(`File validation passed for ${fileName}: ${mimeType}`, { detectedMimeType: validationResult.detectedMimeType, declaredMimeType: validationResult.declaredMimeType, }); const { jobId } = await this.service.processFileUpload( agentId, userId, fileId, fileName, mimeType, fileBuffer, fileSize ); logger.info( `File upload queued with job ID: ${jobId} for file: ${fileName}` ); return { jobId }; }, 'upload'); } @Post('list') async listFiles( @Body('agentId') agentId: string, @Req() req: FastifyRequest ) { logger.info('files.list called'); const userId = ControllerHelpers.getUserId(req); await ControllerHelpers.verifyAgentOwnership( this.agentFactory, agentId, userId ); return this.service.listFiles(agentId, userId); } @Post('get') async getFile( @Body('agentId') agentId: string, @Body('fileId') fileId: string, @Req() req: FastifyRequest ) { logger.info('files.get called'); const userId = ControllerHelpers.getUserId(req); await ControllerHelpers.verifyAgentOwnership( this.agentFactory, agentId, userId ); return this.service.getFile(agentId, fileId, userId); } @Post('delete') async deleteFile( @Body('agentId') agentId: string, @Body('fileId') fileId: string, @Req() req: FastifyRequest ) { logger.info('files.delete called'); const userId = ControllerHelpers.getUserId(req); await ControllerHelpers.verifyAgentOwnership( this.agentFactory, agentId, userId ); await this.service.deleteFile(agentId, fileId, userId); return { deleted: true }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KasarLabs/snak'

If you have feedback or need assistance with the MCP directory API, please join our Discord server