Skip to main content
Glama
webdav.tools.ts10.7 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { getClient } from '../utils/client-manager.js'; import { WebDAVClient } from '../client/webdav.js'; import { prefixToolName } from '../utils/tool-naming.js'; import { SearchEngine } from '../utils/search-engine.js'; import { SearchScope } from '../models/webdav-search.js'; /** * Register WebDAV tools with the MCP server * @param server MCP server instance */ export function registerWebDAVTools(server: McpServer) { server.tool( prefixToolName('webdav_list_directory'), 'List files and directories in Nextcloud', { path: z.string().describe('The path to list (e.g., "/" for root)'), }, async ({ path }) => { const contents = await getClient(WebDAVClient).listDirectory(path); return { content: [ { type: 'text', text: JSON.stringify(contents, null, 2), }, ], }; } ); server.tool( prefixToolName('webdav_read_file'), 'Read content of a file from Nextcloud', { path: z.string().describe('The path to the file to read'), }, async ({ path }) => { const content = await getClient(WebDAVClient).readFile(path); return { content: [ { type: 'text', text: content, }, ], }; } ); server.tool( prefixToolName('webdav_write_file'), 'Write content to a file in Nextcloud', { path: z.string().describe('The path to the file to write'), content: z.string().describe('The content to write to the file'), }, async ({ path, content }) => { await getClient(WebDAVClient).writeFile(path, content); return { content: [ { type: 'text', text: JSON.stringify({ status_code: 200, message: `File written successfully to ${path}`, }, null, 2), }, ], }; } ); server.tool( prefixToolName('webdav_create_directory'), 'Create a new directory in Nextcloud', { path: z.string().describe('The path of the directory to create'), }, async ({ path }) => { await getClient(WebDAVClient).createDirectory(path); return { content: [ { type: 'text', text: JSON.stringify({ status_code: 200, message: `Directory created successfully at ${path}`, }, null, 2), }, ], }; } ); server.tool( prefixToolName('webdav_delete_resource'), 'Delete a file or directory from Nextcloud', { path: z.string().describe('The path to the file or directory to delete'), }, async ({ path }) => { await getClient(WebDAVClient).deleteResource(path); return { content: [ { type: 'text', text: JSON.stringify({ status_code: 200, message: `Resource deleted successfully at ${path}`, }, null, 2), }, ], }; } ); server.tool( prefixToolName('webdav_search_files'), 'Search for files across Nextcloud using unified search - supports filename, content, and metadata search', { query: z.string().describe('Search terms (supports multiple words, e.g., "budget report 2024")'), searchIn: z.array(z.enum(['filename', 'content', 'metadata'])) .optional() .default(['filename', 'content']) .describe('What to search in: filename, content, and/or metadata'), fileTypes: z.array(z.string()) .optional() .describe('Filter by file extensions (e.g., ["pdf", "txt", "md", "js"])'), basePath: z.string() .optional() .default('/') .describe('Limit search to specific directory (e.g., "/Documents", "/Projects")'), limit: z.number() .optional() .default(50) .describe('Maximum number of results to return'), includeContent: z.boolean() .optional() .default(false) .describe('Include content preview in results (for text files)'), caseSensitive: z.boolean() .optional() .default(false) .describe('Whether search should be case sensitive'), sizeRange: z.object({ min: z.number().optional().describe('Minimum file size in bytes'), max: z.number().optional().describe('Maximum file size in bytes') }).optional() .describe('Filter by file size range'), dateRange: z.object({ from: z.string().optional().describe('Start date (ISO format: YYYY-MM-DD)'), to: z.string().optional().describe('End date (ISO format: YYYY-MM-DD)') }).optional() .describe('Filter by last modified date range'), quickSearch: z.boolean() .optional() .default(true) .describe('Use quick search mode (faster, limited depth) for root directory searches'), maxDepth: z.number() .optional() .default(3) .describe('Maximum directory depth to search (1-10, default: 3)') }, async (options) => { const searchStartTime = Date.now(); console.log('WebDAV unified search started with options:', options); try { // Add timeout handling for the entire operation const SEARCH_TIMEOUT = 20000; // 20 seconds const searchPromise = (async () => { // Parse date ranges if provided const searchOptions = { ...options, searchIn: options.searchIn as SearchScope[], dateRange: options.dateRange ? { from: options.dateRange.from ? new Date(options.dateRange.from) : undefined, to: options.dateRange.to ? new Date(options.dateRange.to) : undefined } : undefined }; // Optimize search parameters for root directory searches if (options.basePath === '/' || !options.basePath) { console.log('Root directory search detected, applying optimizations'); // For root searches, use more aggressive limits if (options.quickSearch !== false) { searchOptions.limit = Math.min(options.limit || 25, 25); console.log(`Quick search enabled, limiting results to ${searchOptions.limit}`); } } // Create search engine instance const searchEngine = new SearchEngine(); // Perform the search return await searchEngine.search(searchOptions); })(); const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => { reject(new Error('Search operation timed out after 20 seconds. Try using a more specific directory path or enabling quickSearch.')); }, SEARCH_TIMEOUT); }); const results = await Promise.race([searchPromise, timeoutPromise]); // Format results for output const formattedResults = results.map(result => { const baseResult = { path: result.file.path, name: result.file.name, size: result.file.size, lastModified: result.file.lastModified.toISOString(), mimeType: result.file.mimeType, extension: result.file.extension, isDirectory: result.file.isDirectory, matchType: result.matchType, relevanceScore: Math.round(result.relevanceScore * 100) / 100, highlights: result.highlights }; // Add content preview if requested and available if (options.includeContent && result.contentPreview) { return { ...baseResult, contentPreview: result.contentPreview.substring(0, 500) // Limit preview size }; } return baseResult; }); const searchDuration = Date.now() - searchStartTime; const searchStats = { query: options.query, searchScope: options.searchIn, totalResults: results.length, searchDurationMs: searchDuration, searchTime: new Date().toISOString(), basePath: options.basePath || '/', quickSearchEnabled: options.quickSearch !== false && (options.basePath === '/' || !options.basePath), ...(options.fileTypes && { fileTypesFilter: options.fileTypes }), ...(options.sizeRange && { sizeRangeFilter: options.sizeRange }), ...(options.dateRange && { dateRangeFilter: options.dateRange }) }; console.log(`Search completed in ${searchDuration}ms with ${results.length} results`); return { content: [ { type: 'text', text: JSON.stringify({ searchStats, results: formattedResults }, null, 2), }, ], }; } catch (error) { const searchDuration = Date.now() - searchStartTime; console.error(`WebDAV search failed after ${searchDuration}ms:`, error); let errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; let suggestions = [ 'Try using a more specific directory path instead of root "/" ', 'Enable quickSearch mode for faster results', 'Reduce the file type filters or search scope', 'Use a smaller limit parameter' ]; // Add specific suggestions based on error type if (errorMessage.includes('timeout')) { suggestions = [ 'The search timed out. Try searching in a specific directory instead of root.', 'Use quickSearch: true for faster results with limited depth', 'Reduce the search scope by specifying fileTypes', 'Try searching in a subdirectory like "/Documents" instead of "/"' ]; } return { content: [ { type: 'text', text: JSON.stringify({ error: 'Search failed', message: errorMessage, searchDurationMs: searchDuration, suggestions, quickTips: { forRootSearch: 'Use basePath like "/Documents" instead of "/" for faster searches', forLargeResults: 'Add fileTypes filter like ["pdf", "txt"] to narrow results', forTimeout: 'Try quickSearch: true and smaller limit values' } }, null, 2), }, ], }; } } ); }

Latest Blog Posts

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/hithereiamaliff/mcp-nextcloud'

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