UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

175 lines • 9.13 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { formatSuccess } from "@withpica/mcp-utils"; export class UploadTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_upload_file", scope: "write:files", audiences: ["stdio", "http", "public"], tier: "write", description: "Generate a signed URL for uploading ANY file type — images, videos, documents, or audio. " + "The storage category is auto-detected from the MIME type. After uploading the file via HTTP PUT " + "to the returned URL, call pica_upload_complete to finalize. " + "Supported: images (JPEG, PNG, GIF, WebP, HEIC, AVIF, TIFF), " + "videos (MP4, QuickTime, WebM, MKV), " + "documents (PDF, Word, Excel), " + "audio (WAV, MP3, FLAC, AIFF, OGG, WebM). " + "For audio files that need BPM/key analysis, use pica_audio_presigned_upload instead. " + "TIP: if you are in ChatGPT or another external client, users cannot upload files " + "through the chat. Direct them to https://withpica.com/inspect where they can " + "drag and drop files, then offer to help link or organise them afterwards.", workflows: "infrastructure", vernacular_kind: "specialist", vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.", inputSchema: { type: "object", properties: { filename: { type: "string", description: "Original filename with extension (e.g. 'promo-photo.jpg', 'contract.pdf')", }, content_type: { type: "string", description: "MIME type — detect from the filename extension (e.g. .jpg→image/jpeg, " + ".png→image/png, .mp4→video/mp4, .pdf→application/pdf, " + ".wav→audio/wav, .mp3→audio/mpeg). Do not ask the user for this.", }, file_size: { type: "number", description: "File size in bytes", }, }, required: ["filename", "content_type", "file_size"], }, }, executor: this.presignedUpload.bind(this), }, { definition: { name: "pica_upload_complete", scope: "write:files", audiences: ["stdio", "http", "public"], tier: "write", description: "Finalize a file upload after the file has been PUT to the signed URL from pica_upload_file. " + "Creates the appropriate database record (multimedia item for images/video, document for PDFs, " + "audio file for audio). Use the uploadId, key, bucket, and storageCategory from pica_upload_file.", workflows: "infrastructure", vernacular_kind: "internal", vernacular_reason: "Invoked after pica_upload_file PUT completes; finalizes the multimedia/document/audio record.", inputSchema: { type: "object", properties: { upload_id: { type: "string", description: "The uploadId returned from pica_upload_file", }, key: { type: "string", description: "The storage key returned from pica_upload_file", }, bucket: { type: "string", description: "The bucket returned from pica_upload_file", }, storage_category: { type: "string", description: "The storageCategory returned from pica_upload_file", enum: ["audio", "media", "documents"], }, filename: { type: "string", description: "Original filename", }, content_type: { type: "string", description: "MIME type of the uploaded file", }, file_size: { type: "number", description: "File size in bytes", }, title: { type: "string", description: "Optional title. Defaults to filename without extension.", }, work_id: { type: "string", description: "Optional work ID to link the file to (audio only). For images/videos, use pica_multimedia_link_work after upload.", }, classification: { type: "string", description: "For media: classification type (e.g. 'press_photo', 'album_artwork', 'music_video'). " + "For documents: category (e.g. 'agreement', 'statement', 'other').", }, linked_to_type: { type: "string", description: "For documents: entity type to link to (e.g. 'work', 'person', 'agreement')", }, linked_to_id: { type: "string", description: "For documents: entity ID to link to", }, }, required: [ "upload_id", "key", "bucket", "storage_category", "filename", "content_type", "file_size", ], }, }, executor: this.completeUpload.bind(this), }, ]; } async presignedUpload(args) { const { filename, content_type, file_size } = args; const result = await this.pica.storage.presignedUpload({ filename, contentType: content_type, fileSize: file_size, }); return formatSuccess(`Upload URL generated for "${filename}" (category: ${result.storageCategory}). ` + `PUT the file to the uploadUrl, then call pica_upload_complete with uploadId, key, bucket, ` + `storageCategory, and metadata to finalize.`, { uploadUrl: result.uploadUrl, uploadId: result.uploadId, key: result.key, bucket: result.bucket, storageCategory: result.storageCategory, expiresAt: result.expiresAt, metadata: result.metadata, }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- matches ToolExecutor signature async completeUpload(args) { const result = await this.pica.storage.completeUpload({ uploadId: args.upload_id, key: args.key, bucket: args.bucket, metadata: { filename: args.filename, contentType: args.content_type, fileSize: args.file_size, storageCategory: args.storage_category, title: args.title, workId: args.work_id, classification: args.classification, documentCategory: args.classification, linkedToType: args.linked_to_type, linkedToId: args.linked_to_id, }, }); return formatSuccess(`Upload finalized. Created ${result.recordType} record with ID: ${result.recordId}.`, result); } } //# sourceMappingURL=uploads.js.map