# Zipline MCP Server An MCP (Model Context Protocol) server designed specifically to interact with [Zipline](https://zipline.diced.sh/), allowing you to upload files to a Zipline-compatible host. This server provides tools for uploading files, validating them, and generating upload commands. ## Features - Upload files to a Zipline instance - Validate files before uploading - Preview upload commands - Get only the download URL after upload - List and search user files stored on the Zipline server - List and manage remote folders on the Zipline server - Support for multiple file types, including: - Text/code: txt, md, gpx, html, json, xml, csv, js, css, py, sh, yaml, yml - Images: png, jpg, jpeg, gif, webp, svg, bmp, tiff, ico, heic, avif ## Installation ### Global Installation To install the server globally so you can use it with `npx`: ```bash npm install -g zipline-mcp ``` ### Local Installation To install the server as a dependency in your project: ```bash npm install zipline-mcp ``` ## Usage ### Using with `npx` If you installed the server globally, you can run it directly: ```bash npx zipline-mcp ``` ### Adding to MCP Client Configuration To use this server with an MCP client (like Claude Desktop), you need to add it to your client's configuration file. ### MCP-Zipline Integration Guide #### Quickstart Configuration Add this minimal configuration to your MCP client (e.g., Claude Desktop): ```yaml # YAML format mcpServers: zipline: command: npx args: ['-y', 'zipline-mcp'] environment: ZIPLINE_TOKEN: 'your-api-token-here' ZIPLINE_ENDPOINT: 'http://localhost:3000' ``` ```json // JSON format { "mcpServers": { "zipline": { "command": "npx", "args": ["-y", "zipline-mcp"], "environment": { "ZIPLINE_TOKEN": "your-api-token-here", "ZIPLINE_ENDPOINT": "http://localhost:3000" } } } } ``` #### Detailed Configuration Options ##### Required Settings - `command`: The executable to run (typically "npx") - `args`: Array containing ["zipline-mcp"] - `environment.ZIPLINE_TOKEN`: Your Zipline API token - `environment.ZIPLINE_ENDPOINT`: The URL of your Zipline server (default is "http://localhost:3000") ##### Optional Settings - `environment.ZIPLINE_FORMAT`: File naming format. Supported values: "random", "uuid", "date", "name", "gfycat" (alias for "random-words"), "random-words". Defaults to "random". - `environment.ZIPLINE_ENDPOINT`: Custom Zipline server URL - `environment.ZIPLINE_DISABLE_SANDBOXING`: Disable per-user sandboxing for the tmp_file_manager tool. Set to "true" to disable sandboxing and use the shared `~/.zipline_tmp` directory for all users. Defaults to "false" (sandboxing enabled). - `environment.ALLOWED_EXTENSIONS`: **Override the list of allowed file extensions for upload and validation.** Provide a comma-separated list (e.g., `.txt,.md,.pdf,.docx`). If set, this will replace the default list of allowed extensions. Useful for restricting or expanding supported file types in your deployment. - `environment.ZIPLINE_ALLOWED_ROOTS`: **Directories `upload_file_to_zipline` and `validate_file` may read.** Comma-separated absolute paths (for example `/home/me/uploads,/var/zipline/inbox`). Each path is resolved with `realpath`, and so is the file. A symlink is accepted only when its target stays inside one of these directories. An entry that does not exist is ignored and written to the server log; it does not add any readable path. When unset, the only allowed directory is the home directory of the user running the server. #### Security Best Practices 1. Store API tokens in environment variables, not in config files 2. Use HTTPS for all connections to Zipline servers 3. Set `ZIPLINE_ALLOWED_ROOTS` to the directories the agent is supposed to upload from. The server refuses every other path, including a symlink that points outside those directories. 4. `download_external_url` resolves DNS on every request and every redirect. If any address is loopback, private, link-local, or a cloud metadata address, the download is refused. The connection uses the address from that lookup, so a later lookup cannot retarget it. #### Troubleshooting Common Issues ##### Authentication Failures - Symptom: "Invalid authorization token" errors - Solutions: - Verify token validity - Check for typos in the ZIPLINE_TOKEN value - Ensure token is passed via environment variable ## Architecture Overview The Zipline MCP Server acts as a bridge between MCP clients and Zipline's file hosting service. Here's how the components interact: ```mermaid graph LR A[MCP Client] --> B[MCP Server] B --> C[Zipline API] C --> D[(Zipline Storage)] ``` ### Key Components 1. **MCP Client**: Initiates file upload requests (e.g., Claude Desktop) 2. **MCP Server**: - Validates files locally - Generates upload commands - Handles authentication with Zipline 3. **Zipline API**: Processes upload requests and returns URLs 4. **Zipline Storage**: Persistent file storage system ### Typical Workflow 1. Client sends file path to MCP server 2. Server validates file type and size 3. Server constructs authenticated API request 4. Zipline processes upload and returns URL 5. Server formats response for MCP client ### Format Options The `format` parameter controls how uploaded files are named on the Zipline server. Here are the supported formats: #### `random` Generates a random filename with characters, with length defined by the server configuration. This is the default format. #### `uuid` Generates a UUID-based filename (e.g., `550e8400-e29b-41d4-a716-446655440000`). #### `date` Uses the current date/time formatted according to the server's default date format. Note that date formatting is handled entirely by the Zipline server. #### `name` Uses the original filename without its extension (e.g., `document.txt` becomes `document`). #### `gfycat` Alias for `random-words`. Provides human-readable "random words" style filenames. #### `random-words` Generates human-readable filenames using random words (e.g., `happy-purple-elephant`). #### Format Normalization and Validation - **Case-insensitive**: All format values are case-insensitive (e.g., `UUID`, `uuid`, and `Uuid` are equivalent). - **Alias handling**: `gfycat` is automatically mapped to `random-words`. - **Error handling**: Invalid format values will result in an "Invalid format" error. - **Default behavior**: If the format parameter is omitted, the server uses its configured default format (typically `random`). #### Examples ```bash # Use UUID format upload_file_to_zipline(filePath: "document.txt", format: "uuid") # Use human-readable format (via alias) upload_file_to_zipline(filePath: "document.txt", format: "gfycat") # Case-insensitive usage upload_file_to_zipline(filePath: "document.txt", format: "RANDOM-WORDS") ``` ### Enhanced Upload Options The Zipline MCP Server now supports additional optional headers that provide enhanced control over file uploads. These headers are sent as `x-zipline-*` headers to the Zipline server. #### `x-zipline-deletes-at` Controls when the uploaded file should be automatically deleted. **Formats:** - **Relative duration**: Strings like "1d" (1 day), "2h" (2 hours), "30m" (30 minutes) - **Absolute date**: ISO-8601 format with "date=" prefix, e.g., "date=2025-12-31T23:59:59Z" **Examples:** ```javascript // Delete after 24 hours uploadFile({ filePath: 'document.txt', format: 'random', deletesAt: '1d', }); // Delete at specific date uploadFile({ filePath: 'document.txt', format: 'random', deletesAt: 'date=2025-12-31T23:59:59Z', }); ``` **Validation:** - Relative durations must be positive and use valid units (d, h, m) - Absolute dates must be in ISO-8601 format and specify a future date - Invalid formats will be rejected with descriptive error messages #### `x-zipline-password` Protects the uploaded file with a password. When this header is provided, users will need to enter the password to access the file. **Format:** - Non-empty string value **Examples:** ```javascript uploadFile({ filePath: 'secret.txt', format: 'random', password: 'my-secret-password', }); ``` **Validation:** - Password must be a non-empty string - Whitespace-only passwords are rejected - **Security**: Passwords are never logged or exposed in error messages #### `x-zipline-max-views` Limits the number of times a file can be viewed before it becomes unavailable. Each successful view decrements the counter. **Format:** - Non-negative integer (0 or greater) **Examples:** ```javascript // Allow 10 views uploadFile({ filePath: 'document.txt', format: 'random', maxViews: 10, }); // Allow single view (disposable link) uploadFile({ filePath: 'document.txt', format: 'random', maxViews: 1, }); ``` **Validation:** - Must be an integer ≥ 0 - Negative numbers and non-integer values are rejected - When counter reaches 0, file becomes eligible for removal #### `x-zipline-folder` Specifies the ID of the folder where the upload should be placed. The folder must exist on the Zipline server. **Format:** - Alphanumeric string (letters and numbers only) **Examples:** ```javascript uploadFile({ filePath: 'document.txt', format: 'random', folder: 'myfolder123', }); ``` **Validation:** - Must be a non-empty alphanumeric string - Special characters and whitespace are rejected - If the specified folder doesn't exist, the upload will fail #### Combining Multiple Headers You can combine multiple headers for enhanced control: ```javascript uploadFile({ filePath: 'document.txt', format: 'random', deletesAt: '7d', // Delete after 7 days password: 'secret123', // Password protect maxViews: 5, // Allow 5 views folder: 'shared', // Place in "shared" folder }); ``` #### Error Handling All headers are validated locally before the upload request is sent to the server. If any header is invalid: - The upload is aborted immediately - A descriptive error message is returned - No HTTP request is made to the Zipline server - The error message explains exactly what needs to be fixed #### Backward Compatibility All enhanced headers are **optional**. Existing code that doesn't use these headers will continue to work exactly as before. The new functionality only activates when you explicitly provide these parameters. ### Available Tools This server provides the following tools: #### `upload_file_to_zipline` Uploads a file to the Zipline server and returns a detailed success message. - `filePath`: Path to the file to upload. The path must resolve inside `ZIPLINE_ALLOWED_ROOTS` (the home directory when that variable is unset). The server reads the real path after resolving symlinks. Supported extensions: txt, md, gpx, html, json, xml, csv, js, css, py, sh, yaml, yml, png, jpg, jpeg, gif, webp, svg, bmp, tiff, ico, heic, avif. Binary types are accepted only when the file contents identify that type. Legacy Word, Excel, and PowerPoint files are accepted when the bytes are a Compound File Binary container. AVI is accepted when the bytes are an AVI container (`video/vnd.avi`). Matroska is accepted when the bytes are Matroska (`video/matroska`), and WebM when they are WebM. If a Matroska or WebM file starts with an EBML header but the document type is past the first 4100 bytes, the server keeps reading that file until the type is identified. The extension alone is not enough. Text types are accepted only when the bytes contain no NUL. A NUL byte does not skip the secret scan. - `originalName`: (optional) Original filename to preserve during download. This parameter is sent as the `x-zipline-original-name` header to the Zipline server. The original filename will be used when downloading the file, not when storing it. Must be a non-empty string without path separators. **Example Prompts for Users:** - "Upload the file '/path/to/document.pdf' to Zipline" - "Upload my file at '/home/user/images/photo.jpg' and preserve the original name" - "I want to upload '/path/to/data.csv' with the original name 'sales-data.csv'" - "Upload file '/path/to/script.py' to Zipline" - "Can you upload '/path/to/report.docx' for me?" #### `validate_file` Checks if a file exists and is suitable for upload. - `filePath`: Path to the file to validate. The same `ZIPLINE_ALLOWED_ROOTS` rule as upload applies: the real path, after symlinks, must stay inside an allowed directory. Supported extensions: txt, md, gpx, html, json, xml, csv, js, css, py, sh, yaml, yml, png, jpg, jpeg, gif, webp, svg, bmp, tiff, ico, heic, avif. The same content rules as upload apply, including the Compound File, AVI, Matroska, and WebM cases. Unidentified non-text content is rejected rather than trusted because the extension matches. **Example Prompts for Users:** - "Check if '/path/to/document.pdf' is valid for upload" - "Validate the file at '/home/user/images/photo.jpg'" - "Is '/path/to/data.csv' suitable for uploading to Zipline?" - "Can you validate '/path/to/script.py' for me?" - "Check if '/path/to/report.docx' can be uploaded" #### `list_user_files` Lists and searches files that you have previously uploaded to the Zipline server. Use this tool to find file IDs that can then be used with `get_user_file`, `update_user_file`, and `delete_user_file` tools. You can also obtain file IDs by using `get_user_file` if you already know the file name. - `page`: (optional) Page number for pagination (default: 1) - `perPage`: (optional) Number of items per page (default: 20) - `searchField`: (optional) Field to search in (e.g., "name", "originalName", "type") - `searchQuery`: (optional) Search query string - `filter`: (optional) Filter by file type or other criteria - `sort`: (optional) Sort field (e.g., "createdAt", "size", "name") - `order`: (optional) Sort order ("asc" or "desc", default: "desc") **Example Prompts for Users:** - "List all my files on Zipline" - "Show me the first page of my uploaded files" - "Search for files with 'document' in the name" - "Find all PDF files that I've uploaded" - "List my files sorted by creation date, newest first" - "Show me 10 files per page, page 2" - "Search for files with 'report' in the original name" - "List my files sorted by size, largest first" - "Find all image files I've uploaded" #### `tmp_file_manager` Minimal, sandboxed file management with per-user isolation. Each user gets their own sandbox directory based on their ZIPLINE_TOKEN. Only bare filenames are allowed (no subdirectories or path traversal). All operations are strictly limited to the user's sandbox. - `command`: One of: - `LIST` — List all files in the user's sandbox - `CREATE ` — Create or overwrite a file (optionally provide `content`) - `OPEN ` or `READ ` — Read file content (max 1MB) - `PATH ` — Get the absolute path of a file in the sandbox - `DELETE ` — Delete a file from the sandbox - `content`: (optional) String content for `CREATE` **Examples:** - `LIST` - `CREATE notes.txt` (with or without content) - `OPEN notes.txt` - `READ notes.txt` - `PATH notes.txt` - `DELETE notes.txt` **Example Prompts for Users:** - "List all files in my sandbox" - "Create a file named 'notes.txt' with content 'Meeting notes from today'" - "Read the content of 'config.json'" - "Get the absolute path of 'data.csv'" - "Delete the file 'temp.txt'" - "Show me all files in my temporary directory" - "Create a new file called 'draft.md' with some content" - "Open and read the 'settings.json' file" - "What's the full path to 'output.txt'?" - "Remove 'old_file.txt' from my sandbox" **Sandboxing Configuration:** - Per-user sandboxing is enabled by default - To disable sandboxing and use the shared `~/.zipline_tmp` directory for all users, set `ZIPLINE_DISABLE_SANDBOXING=true` in your environment - When sandboxing is disabled, session locking and TTL cleanup are also disabled **Per-User Sandboxing (when enabled):** - Each user gets a unique sandbox directory at `~/.zipline_tmp/users/{hash}` where `hash` is a SHA-256 hash of the user's ZIPLINE_TOKEN - Sandboxes are automatically created with 0700 permissions for security - All file operations are isolated within the user's sandbox - Sandboxes older than 24 hours are automatically cleaned up **Session Locking (when sandboxing is enabled):** - Each user's sandbox is locked during operations to prevent concurrent access - Locks automatically expire after 30 minutes - The system prevents race conditions when multiple clients access the same sandbox **Safety:** - Only bare filenames allowed (no `/`, `\`, `..`, or absolute paths) - Any attempt to access outside the user's sandbox is refused with an explicit error - Files larger than 1MB cannot be read - Subdirectories are not supported - Each user's sandbox is isolated from others (when sandboxing is enabled) ### download_external_url A new tool has been added: `download_external_url`. It lets the MCP server (and therefore models) download an external HTTP(S) URL into the caller's sandbox and returns the absolute filesystem path to the saved file. - Tool name: `download_external_url` - Purpose: Download an external HTTP(S) resource into the per-user sandbox and return the absolute path where the file was stored. - Input schema: - `url` (string) — required — HTTP or HTTPS URL to download - `timeoutMs` (number) — optional — request timeout in milliseconds (default: 30000) - `maxFileSizeBytes` (number) — optional — maximum allowed file size in bytes (default: 100 _ 1024 _ 1024 = 100 MB) - Behavior: - Validates the provided URL and only allows `http:` and `https:` schemes. - Uses an AbortController to enforce the configured timeout. - Checks the `Content-Length` header when present and rejects downloads that declare a size larger than the configured maximum. - Downloads into the user's sandbox directory (same sandbox used by the `tmp_file_manager` tool) and returns the absolute path, e.g. `/home/user/.zipline_tmp/users//file.ext`. - If the remote resource does not provide a filename, a safe deterministic name is used. - Partial files are cleaned up on failure to avoid leaving incomplete artifacts in the sandbox. - Structured logs are emitted via the existing sandbox logging facility for observability. Usage example (MCP tool call) - Example input: { "url": "https://example.com/files/document.pdf", "timeoutMs": 30000, "maxFileSizeBytes": 104857600 } - Example successful response content: "✅ DOWNLOAD COMPLETE\n\nLocal path: /home/user/.zipline_tmp/users/abc123/document.pdf" Security & safety considerations - Only `http` and `https` are accepted. Other schemes (ftp, file, etc.) are rejected. - Before each connection, including after a redirect, the server resolves the hostname and refuses the download if any address is loopback, private, link-local, or a cloud metadata address (for example `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254`). - The socket connects to the address returned by that lookup. The original hostname is sent as the `Host` header and as the TLS server name. A DNS change after the check cannot move the connection. - Default file size limit: 100 MB. You can override via `maxFileSizeBytes` but be cautious. - The downloader follows redirects but enforces the maximum redirect behavior in code (to avoid redirect loops). Each redirect is resolved and checked again. - When the remote server returns an error status, only the first 1 MiB of the response body is kept. A successful download still uses `maxFileSizeBytes` (default 100 MB). - All downloads are saved inside the per-user sandbox; filenames are validated and sanitized using the same rules as `tmp_file_manager` (no path separators, no dot segments, no absolute paths). - The server logs download activity with sanitized paths (user portion masked) for observability; secrets (such as tokens) are never logged. - If you require domain allow-listing to restrict where the server can download from, consider adding a hostname whitelist at the MCP server configuration level before enabling this tool for production usage. **Example Prompts for Users:** - "Download the PDF from 'https://example.com/document.pdf' to my sandbox" - "Download 'https://example.com/data.csv' with a timeout of 60 seconds" - "Download the image from 'https://example.com/image.jpg' with a max file size of 50MB" - "Download the file from 'https://example.com/report.pdf' and save it to my sandbox" - "Can you download 'https://example.com/config.json' for me?" - "Download 'https://example.com/archive.zip' with a custom timeout of 45 seconds" - "I need to download 'https://example.com/data.json' with a max size of 10MB" ### list_user_files A new tool has been added: `list_user_files`. It allows you to list and search files that you have previously uploaded to the Zipline server. - Tool name: `list_user_files` - Purpose: List and search user files stored on the Zipline server with support for pagination, filtering, sorting, and searching. - Input schema: - `page` (number) — optional — Page number for pagination (default: 1) - `perPage` (number) — optional — Number of items per page (default: 20) - `searchField` (string) — optional — Field to search in (e.g., "name", "originalName", "type") - `searchQuery` (string) — optional — Search query string - `filter` (string) — optional — Filter by file type or other criteria - `sort` (string) — optional — Sort field (e.g., "createdAt", "size", "name") - `order` (string) — optional — Sort order ("asc" or "desc", default: "desc") - Behavior: - Fetches files from the Zipline API with the specified parameters - Supports pagination to handle large numbers of files - Allows searching within specific fields using searchField and searchQuery - Supports filtering and sorting of results - Returns a structured response with file information and pagination metadata Usage example (MCP tool call) - Example input: ```json { "page": 1, "perPage": 10, "searchField": "name", "searchQuery": "document", "sort": "createdAt", "order": "desc" } ``` - Example successful response content: ``` 📁 USER FILES (Page 1 of 3) 1. ⭐ document.pdf 🆔 ID: file123 📅 Created: 1/15/2025 📊 Size: 1.2 MB 🏷️ Type: application/pdf 👁️ Views: 5 🔗 URL: https://zipline.example.com/u/file123 2. image.png 🆔 ID: img456 📅 Created: 1/14/2025 📊 Size: 2.1 MB 🏷️ Type: image/png 👁️ Views: 12 🔗 URL: https://zipline.example.com/u/img456 3. data.csv 🆔 ID: csv789 📅 Created: 1/13/2025 📊 Size: 500.0 KB 🏷️ Type: text/csv 👁️ Views: 3 🔗 URL: https://zipline.example.com/u/csv789 Total files: 25 | Showing: 3 ``` **Note:** The URLs in the response are normalized to ensure proper formatting regardless of the ZIPLINE_ENDPOINT configuration. The normalization handles trailing slashes and ensures consistent URL construction. File Model The tool returns files with the following structure: - `id`: Unique identifier for the file (used for operations like get_user_file, update_user_file, and delete_user_file) - `name`: The filename as stored on the server - `originalName`: The original filename when uploaded - `size`: File size in bytes - `type`: MIME type of the file - `url`: Direct URL to access the file - `createdAt`: When the file was uploaded (ISO 8601 format) - `expiresAt`: When the file will expire (if applicable) - `maxViews`: Maximum view limit (if applicable) - `views`: Current view count Search and Filter Options The tool provides powerful search and filtering capabilities: - **Search**: Use `searchField` and `searchQuery` to search within specific fields - Supported search fields: "name", "originalName", "type" - Search is case-insensitive and supports partial matches - **Filter**: Use the `filter` parameter to filter by file type or other criteria - **Sort**: Use `sort` and `order` to sort results by any field - Supported sort fields: "createdAt", "size", "name", "views" - Order can be "asc" (ascending) or "desc" (descending) Notes for integrators - This tool is exported by the MCP server and can be invoked programmatically by MCP clients or models. - The implementation lives in [`src/userFiles.ts`](src/userFiles.ts:1) and the MCP registration is in [`src/index.ts`](src/index.ts:1). - Tests were added under `src/userFiles.test.ts` to exercise pagination, searching, filtering, sorting, and error handling. ### get_user_file Information about the tool: `get_user_file`. It allows you to retrieve detailed information about a specific file stored on the Zipline server. Use `list_user_files` or the file name to find the file ID you need for this tool. - Tool name: `get_user_file` - Purpose: Get detailed information about a specific file stored on the Zipline server - Input schema: - `id` (string) — required — File ID to retrieve information for. Obtain this from the `list_user_files` tool. - Behavior: - Fetches file information from the Zipline API using the file ID - Returns comprehensive file metadata including size, type, views, favorites status, and more - Handles URL encoding of special characters in file IDs Usage example (MCP tool call) - Example input: ```json { "id": "file123" } ``` - Example successful response content: ``` 📁 FILE INFORMATION 📁 document.pdf 🆔 ID: file123 📅 Created: 1/15/2025 📊 Size: 1.2 MB 🏷️ Type: application/pdf 👁️ Views: 5 🔗 URL: http://localhost:3000/u/file123 📄 Original Name: my-document.pdf 🏷️ Tags: tag1, tag2 📁 Folder ID: folder123 ``` **Note:** The URL in the response is normalized to ensure proper formatting regardless of the ZIPLINE_ENDPOINT configuration. The normalization handles trailing slashes and ensures consistent URL construction. **Example Prompts for Users:** - "Get detailed information about the file with ID 'abc123'" - "Show me the details for file 'document.pdf'" - "I need to see the metadata for file 'xyz789'" - "What are the properties of the file named 'my-image.jpg'?" - "Get information about the file with ID 'file123'" ### update_user_file Information about the tool: `update_user_file`. It allows you to update properties of a specific file stored on the Zipline server. Use `list_user_files` or the file name to find the file ID you need for this tool. - Tool name: `update_user_file` - Purpose: Update properties of a specific file stored on the Zipline server - Input schema: - `id` (string) — required — File ID to update. Obtain this from the `list_user_files` tool. - `favorite` (boolean) — optional — Set/unset as favorite - `maxViews` (number) — optional — Maximum allowed views (>= 0) - `password` (string | null) — optional — Set password (string), remove password (null) - `originalName` (string) — optional — Set or update the original filename - `type` (string) — optional — Set or update the file MIME type - `tags` (string[]) — optional — Array of tag IDs to set for this file - `name` (string) — optional — Rename the file - Behavior: - Updates file properties using PATCH request to the Zipline API - Only updates fields that are explicitly provided (undefined fields are ignored) - Supports setting or removing passwords by passing string or null - Returns the updated file information Usage example (MCP tool call) - Example input: ```json { "id": "file123", "favorite": true, "maxViews": 10, "password": "secret123" } ``` - Example successful response content: ``` ✅ FILE UPDATED SUCCESSFULLY! ⭐🔒 document.pdf 🆔 ID: file123 📅 Created: 1/15/2025 📊 Size: 1.2 MB 🏷️ Type: application/pdf 👁️ Views: 5/10 📄 Original Name: my-document.pdf 🏷️ Tags: tag1, tag2 📁 Folder ID: folder123 ``` **Note:** The response does not include the URL field. To get the updated file information with the URL, use the `get_user_file` tool. **Example Prompts for Users:** - "Set file 'abc123' as a favorite" - "Update file 'document.pdf' to have a maximum of 10 views" - "Add password protection to file 'xyz789' with password 'secret123'" - "Remove the password from file 'file123'" - "Change the original name of file 'abc123' to 'my-updated-document.pdf'" - "Rename file 'xyz789' to 'new-name.pdf'" - "Set the MIME type of file 'file123' to 'application/pdf'" - "Add tags ['tag1', 'tag2'] to file 'abc123'" - "Update file 'xyz789' to be a favorite with maxViews of 5 and password 'secure'" ### delete_user_file A new tool has been added: `delete_user_file`. It allows you to delete a specific file stored on the Zipline server. Use `list_user_files` or the file name to find the file ID you need for this tool. - Tool name: `delete_user_file` - Purpose: Delete a specific file stored on the Zipline server - Input schema: - `id` (string) — required — File ID to delete. Obtain this from the `list_user_files` tool. - Behavior: - Deletes the file using DELETE request to the Zipline API - Returns the file information that was deleted (for confirmation) - Handles URL encoding of special characters in file IDs Usage example (MCP tool call) - Example input: ```json { "id": "uniqueid123" } ``` - Example successful response content: ``` ✅ FILE DELETED SUCCESSFULLY! ⭐🔒 document.pdf 🆔 ID: uniqueid123 📅 Created: 1/15/2025 📊 Size: 1.2 MB 🏷️ Type: application/pdf 👁️ Views: 5/10 📄 Original Name: my-document.pdf 🏷️ Tags: tag1, tag2 📁 Folder ID: folder123 ``` **Note:** The response does not include the URL field since the file has been deleted. **Example Prompts for Users:** - "Delete file with ID 'abc123'" - "Remove file 'document.pdf' from the server" - "Delete the file named 'my-image.jpg'" - "Permanently delete file 'xyz789'" - "I want to delete the file with ID 'file123'" ### remote_folder_manager A new tool has been added: `remote_folder_manager`. It allows you to list and manage remote folders on the Zipline server. - Tool name: `remote_folder_manager` - Purpose: List and manage remote folders on the Zipline server - Input schema: - `command` (string) — required — Command to execute. Supports: - `LIST` — List all folders on the Zipline server with full details - `ADD ` — Create a new folder with the specified name - `EDIT ` — Edit an existing folder with the specified ID - `INFO ` — Get detailed information about a specific folder by ID - `DELETE ` — Delete an existing folder with the specified ID - `id` (string) — optional — Folder ID to edit (required for EDIT command) - `name` (string) — optional — Folder name (required for ADD command, optional for EDIT command) - `isPublic` (boolean) — optional — Whether the folder is public (default: false, for ADD command, for EDIT command) - `allowUploads` (boolean) — optional — Whether uploads are allowed to the folder (for EDIT command) - `fileId` (string) — optional — File ID to add to the folder (for EDIT command) - `files` (string[]) — optional — Array of file IDs to include in the folder (for ADD command) - `id` (string) — optional — Folder ID to get information for (required for INFO command) - Behavior: - For LIST: Fetches folders from the Zipline API and returns a structured response with full folder details including ID, name, public status, creation/update timestamps, and file count - For ADD: Creates a new folder with the specified name and optional parameters - For EDIT: Modifies an existing folder with the specified ID and optional parameters - When `fileId` is provided: Adds the specified file to the folder - When `name`, `isPublic`, or `allowUploads` are provided: Updates folder properties - For INFO: Retrieves detailed information about a specific folder by its ID, including all folder properties - For DELETE: Permanently deletes an existing folder with the specified ID and returns the deleted folder's information - Returns comprehensive folder information including all available fields - Validates input parameters and provides clear error messages - For INFO command, requires a valid folder ID (not folder name) Usage examples (MCP tool call) **LIST command example:** - Example input: ```json { "command": "LIST" } ``` - Example successful response content: ``` 📁 REMOTE FOLDERS 1. Documents 🆔 ID: folder123 🔒 Private 📅 Created: 2025-01-15T10:30:45.123Z 📝 Updated: 2025-01-20T14:45:30.456Z 📄 Files: 5 2. Images 🆔 ID: folder456 🔒 Public 📅 Created: 2025-01-10T09:15:22.789Z 📝 Updated: 2025-01-18T16:20:11.012Z 📄 Files: 12 3. Public 🆔 ID: No ID 🔒 Public 📅 Created: 2025-01-05T08:00:00.000Z 📝 Updated: 2025-01-05T08:00:00.000Z 📄 Files: 0 Total folders: 3 ``` **ADD command example:** - Example input (using parameters): ```json { "command": "ADD", "name": "My New Folder", "isPublic": false } ``` - Example input (using command arguments): ```json { "command": "ADD My New Folder" } ``` - Example successful response content: ``` ✅ FOLDER CREATED SUCCESSFULLY! 📁 My New Folder 🆔 ID: folder789 🔒 Private 📄 Files: 0 ``` **Note:** Some folders may not have IDs if the Zipline server doesn't assign them. The tool gracefully handles this case by displaying "No ID" for folders without IDs. **EDIT command example:** - Example input (updating folder properties): ```json { "command": "EDIT", "id": "folder123", "name": "Updated Folder Name", "isPublic": true, "allowUploads": true } ``` - Example input (adding a file to a folder): ```json { "command": "EDIT", "id": "folder123", "fileId": "file456" } ``` - Example successful response content (updating properties): ``` ✅ FOLDER UPDATED SUCCESSFULLY! 📁 Updated Folder Name 🆔 ID: folder123 🔒 Public 📤 Uploads Allowed 📄 Files: 3 ``` - Example successful response content (adding a file): ``` ✅ FILE ADDED TO FOLDER SUCCESSFULLY! 📁 Documents 🆔 ID: folder123 🔒 Private 📄 Files: 4 ``` **Note:** The EDIT command supports two modes of operation: 1. **Property editing**: When `name`, `isPublic`, or `allowUploads` are provided, the folder properties are updated via PATCH request. 2. **File addition**: When `fileId` is provided, the specified file is added to the folder via PUT request. These modes can be used separately or together in a single command. **INFO command example:** - Example input: ```json { "command": "INFO", "id": "folder123" } ``` - Example successful response content: ``` 📁 FOLDER INFORMATION 📁 Documents 🆔 ID: folder123 🔒 Private 📅 Created: 2025-01-15T10:30:45.123Z 📝 Updated: 2025-01-20T14:45:30.456Z 📄 Files: 5 📋 Files (IDs): file123, file456, file789, fileabc, filexyz ``` **Note:** The INFO command requires a valid folder ID, not the folder name. Use the LIST command to find the ID of the folder you want to get information about. **DELETE command example:** - Example input: ```json { "command": "DELETE", "id": "folder123" } ``` - Example successful response content: ``` ✅ FOLDER DELETED SUCCESSFULLY! 📁 Documents 🆔 ID: folder123 🔒 Private 📅 Created: 2025-01-15T10:30:45.123Z 📝 Updated: 2025-01-20T14:45:30.456Z 📄 Files: 5 ``` **Note:** The DELETE command requires a valid folder ID, not the folder name. Use the LIST command to find the ID of the folder you want to delete. The folder deletion is permanent and cannot be undone. Folder Model The tool returns folders with the following structure: - `id`: Unique identifier for the folder (optional, may not be present for all folders) - `name`: The folder name - `public`: Whether the folder is public (true/false) - `createdAt`: When the folder was created (ISO 8601 format) - `updatedAt`: When the folder was last updated (ISO 8601 format) - `files`: Array of file IDs contained in the folder Notes for integrators - This tool is exported by the MCP server and can be invoked programmatically by MCP clients or models. - The implementation lives in [`src/remoteFolders.ts`](src/remoteFolders.ts:1) and the MCP registration is in [`src/index.ts`](src/index.ts:1). - Tests were added under `src/remoteFolders.test.ts` to exercise success cases, pagination, error handling, and edge cases for both LIST and ADD commands. **Example Prompts for Users:** - "List all folders on my Zipline server" - "Show me the available folders" - "What folders do I have on the server?" - "List all remote folders" - "Create a new folder named 'Documents'" - "Add a folder called 'Images' to my Zipline server" - "Create a public folder named 'Shared Files'" - "Add a new folder with the name 'Projects'" - "Create a folder named 'Archive' and make it private" - "Edit folder 'folder123' to rename it to 'New Documents'" - "Update folder 'folder456' to make it public and allow uploads" - "Add file 'file789' to folder 'folder123'" - "Change folder 'folder789' to be private and disable uploads" - "Rename folder 'folder456' to 'Archive' and make it public" - "Get detailed information about folder 'folder123'" - "Show me all details for folder with ID 'folder789'" - "What are the properties of folder 'folder456'?" - "Get info about the folder named 'Documents' (first use LIST to get the ID)" - "Delete folder with ID 'folder123'" - "Remove the folder named 'Old Documents' from the server" - "Delete the folder with ID 'folder456'" - "Permanently delete folder 'folder789'" - "I want to delete the folder with ID 'folder123'" ### Sandbox Path Resolution The `tmp_file_manager` tool uses a secure path resolution mechanism to ensure all file operations stay within sandbox boundaries. This is implemented through the `resolveSandboxPath` function which provides the following security guarantees: **Path Validation:** - **Filename Validation**: All filenames are validated to prevent path traversal attacks - **Absolute Path Resolution**: Converts relative filenames to absolute paths within the sandbox - **Boundary Enforcement**: Ensures resolved paths cannot escape the sandbox directory - **Error Handling**: Throws `SandboxPathError` for any violation with clear error messages **Security Features:** - **Input Sanitization**: Rejects filenames containing path separators (`/`, `\`), dot segments (`..`), or absolute paths - **Path Normalization**: Resolves paths to their canonical form to prevent symbolic link attacks - **Containment Verification**: Verifies that the final resolved path is within the sandbox boundaries - **Audit Logging**: All sandbox operations are logged with sanitized paths for security monitoring **Implementation Details:** ```typescript // Example of secure path resolution function resolveSandboxPath(filename: string): string { const userSandbox = getUserSandbox(); // Validate filename first const validationError = validateFilename(filename); if (validationError) { throw new SandboxPathError(validationError); } const resolved = path.resolve(userSandbox, filename); // Security: Ensure resolved path is within sandbox if (!resolved.startsWith(userSandbox)) { throw new SandboxPathError(`Path traversal attempt: ${filename}`); } return resolved; } ``` **Error Examples:** - `CREATE ../evil.txt` → Error: Filenames must not include path separators, dot segments, or be empty. Only bare filenames in ~/.zipline_tmp are allowed. - `OPEN /etc/passwd` → Error: Filenames must not include path separators, dot segments, or be empty. Only bare filenames in ~/.zipline_tmp are allowed. - `READ ../../system/file` → Error: Path traversal attempt: ../../system/file **Logging and Auditing:** All sandbox operations are logged with security-sensitive information sanitized: ``` [2025-01-15T10:30:45.123Z] SANDBOX_OPERATION: FILE_CREATED - notes.txt - Path: /home/user/.zipline_tmp/users/[HASH] - Size: 42 bytes ``` User-specific paths are masked with `[HASH]` to prevent token leakage in logs while maintaining traceability. ## Development ### Prerequisites - Node.js (v18 or higher) - npm or yarn ### Setup 1. Clone the repository: ```bash git clone https://github.com/dorogoy/zipline-mcp.git cd zipline-mcp ``` 2. Install dependencies: ```bash npm install ``` ### Scripts - `npm run build`: Build the TypeScript project. - `npm run start`: Run the built server. - `npm run dev`: Run the server in development mode with `tsx`. - `npm run test`: Run tests in watch mode. - `npm run test:run`: Run tests once. - `npm run lint`: Lint the codebase using ESLint. - `npm run lint:fix`: Automatically fix lint errors where possible. - `npm run format`: Format the codebase with Prettier. - `npm run format:check`: Check if the codebase is formatted. ### Linting #### Running Lint Locally To check for lint errors: ```bash npm run lint ``` To automatically fix fixable issues: ```bash npm run lint:fix ``` Or use the Makefile: ```bash make lint ``` #### Lint in CI - Linting runs automatically on every pull request and before every release. - The CI will fail if there are any lint errors. - Linting is required for merging PRs. #### Interpreting Lint Results - Errors will be shown in the terminal or CI logs. - Fix errors locally using `npm run lint:fix` or by editing the code as indicated. - Warnings do not block CI but should be addressed for code quality. ### Makefile A `Makefile` is provided for convenience: ```bash make install # Install dependencies make build # Build the project make test # Run tests make lint # Lint the code make format # Format the code make clean # Clean build artifacts make publish # Publish to npm ``` ## License MIT