Skip to main content
Glama
masx200
by masx200

webdav_range_request

Read specific byte ranges from files on remote WebDAV servers using HTTP partial content requests to download portions of large files without transferring the entire file.

Instructions

Read a specific byte range from a file on a remote WebDAV server (similar to HTTP 206 Partial Content)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYes
rangeYesByte range in format "bytes=0-499" (first 500 bytes), "bytes=500-" (from byte 500 to end), or "0-499" (range from start to end)

Implementation Reference

  • MCP tool registration for 'webdav_range_request', including schema validation with Zod and the complete inline handler function that orchestrates the range request.
    server.tool(
      "webdav_range_request",
      "Read a specific byte range from a file on a remote WebDAV server (similar to HTTP 206 Partial Content)",
      {
        path: z.string().min(1, "Path must not be empty"),
        range: z.string().describe(
          'Byte range in format "bytes=0-499" (first 500 bytes), "bytes=500-" (from byte 500 to end), or "0-499" (range from start to end)',
        ),
      },
      async ({ path, range }) => {
        try {
          // Check if file exists first
          const exists = await webdavService.exists(path);
          if (!exists) {
            return {
              content: [{
                type: "text",
                text: `Error: File does not exist at ${path}`,
              }],
              isError: true,
            };
          }
    
          // Check if range requests are supported
          const supportsRanges = await webdavService.supportsRangeRequests(path);
          if (!supportsRanges) {
            return {
              content: [{
                type: "text",
                text:
                  `Error: Range requests are not supported for this file or server`,
              }],
              isError: true,
            };
          }
    
          // Perform the range request
          const result = await webdavService.readFileWithRange(path, range);
    
          // Format the response similar to HTTP 206 response
          const response = [
            `=== HTTP 206 Partial Content Simulation ===`,
            `File: ${path}`,
            `Content-Range: ${result.contentRange}`,
            `Accept-Ranges: ${result.acceptRanges ? "bytes" : "none"}`,
            `Content-Length: ${result.content.length}`,
            `Total-Size: ${result.totalSize}`,
            `Range-Request: ${range}`,
            ``,
            `=== Content ===`,
            result.content,
          ].join("\n");
    
          return {
            content: [{
              type: "text",
              text: response,
            }],
          };
        } catch (error) {
          return {
            content: [{
              type: "text",
              text: `Error performing range request: ${(error as Error).message}`,
            }],
            isError: true,
          };
        }
      },
    );
  • The handler function that implements the tool logic: input validation is implicit via schema, checks preconditions, delegates to WebDAV service, formats output as simulated HTTP partial content response.
    async ({ path, range }) => {
      try {
        // Check if file exists first
        const exists = await webdavService.exists(path);
        if (!exists) {
          return {
            content: [{
              type: "text",
              text: `Error: File does not exist at ${path}`,
            }],
            isError: true,
          };
        }
    
        // Check if range requests are supported
        const supportsRanges = await webdavService.supportsRangeRequests(path);
        if (!supportsRanges) {
          return {
            content: [{
              type: "text",
              text:
                `Error: Range requests are not supported for this file or server`,
            }],
            isError: true,
          };
        }
    
        // Perform the range request
        const result = await webdavService.readFileWithRange(path, range);
    
        // Format the response similar to HTTP 206 response
        const response = [
          `=== HTTP 206 Partial Content Simulation ===`,
          `File: ${path}`,
          `Content-Range: ${result.contentRange}`,
          `Accept-Ranges: ${result.acceptRanges ? "bytes" : "none"}`,
          `Content-Length: ${result.content.length}`,
          `Total-Size: ${result.totalSize}`,
          `Range-Request: ${range}`,
          ``,
          `=== Content ===`,
          result.content,
        ].join("\n");
    
        return {
          content: [{
            type: "text",
            text: response,
          }],
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Error performing range request: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    },
  • Input schema using Zod for path (required string) and range (string with description of supported formats).
    {
      path: z.string().min(1, "Path must not be empty"),
      range: z.string().describe(
        'Byte range in format "bytes=0-499" (first 500 bytes), "bytes=500-" (from byte 500 to end), or "0-499" (range from start to end)',
      ),
    },
  • Supporting helper method in WebDAVService that handles the low-level range request: parses range string, validates against file size, creates read stream with range, buffers content, computes Content-Range header.
    async readFileWithRange(
      path: string,
      range: string,
    ): Promise<{
      content: string;
      contentRange: string;
      acceptRanges: boolean;
      totalSize: number;
    }> {
      const fullPath = this.getFullPath(path);
      logger.debug(`Reading file with range: ${fullPath}`, { range });
    
      try {
        // Parse the range header
        const parsedRange = this.parseRangeHeader(range);
        if (!parsedRange) {
          throw new Error("Invalid range format");
        }
    
        // Get file stats first to check total size
        const stats = await this.stat(fullPath);
        const totalSize = stats.size || 0;
    
        // Validate range against file size
        if (parsedRange.start >= totalSize) {
          throw new Error(
            `Range start (${parsedRange.start}) is beyond file size (${totalSize})`,
          );
        }
    
        // Calculate actual end position
        const end = parsedRange.end === undefined
          ? totalSize - 1
          : Math.min(parsedRange.end, totalSize - 1);
    
        // Use createReadStream with range options
        const stream = this.client.createReadStream(fullPath, {
          range: {
            start: parsedRange.start,
            end: parsedRange.end,
          },
        });
    
        // Convert stream to string
        const chunks: Buffer[] = [];
    
        return new Promise((resolve, reject) => {
          stream.on("data", (chunk: Buffer) => {
            chunks.push(chunk);
          });
    
          stream.on("end", () => {
            try {
              const content = Buffer.concat(chunks).toString("utf8");
              const contentRange =
                `bytes ${parsedRange.start}-${end}/${totalSize}`;
    
              logger.debug(`Range request completed: ${fullPath}`, {
                range,
                contentLength: content.length,
                totalSize,
              });
    
              resolve({
                content,
                contentRange,
                acceptRanges: true,
                totalSize,
              });
            } catch (error) {
              reject(
                new Error(
                  `Failed to process stream content: ${(error as Error).message}`,
                ),
              );
            }
          });
    
          stream.on("error", (error) => {
            logger.error(`Stream error for ${fullPath}:`, error);
            reject(new Error(`Stream error: ${error.message}`));
          });
        });
      } catch (error) {
        logger.error(`Error reading file with range ${fullPath}:`, error);
        throw new Error(
          `Failed to read file with range: ${(error as Error).message}`,
        );
      }
    }
  • Helper method that checks if range requests are supported by attempting to stat the file (assumes support if stat succeeds).
    async supportsRangeRequests(path: string = "/"): Promise<boolean> {
      const fullPath = this.getFullPath(path);
      logger.debug(`Checking range request support for: ${fullPath}`);
    
      try {
        // For WebDAV servers, we'll assume range requests are supported
        // if we can successfully read file metadata
        const stats = await this.stat(fullPath);
        return true;
      } catch (error) {
        logger.debug(`Range request support check failed for ${fullPath}`, error);
        return false;
      }
    }

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/masx200/mcp-webdav-server'

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