index.ts•4.87 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync } from 'fs';
const execAsync = promisify(exec);
interface DownloadWebsiteArgs {
  url: string;
  outputPath?: string;
  depth?: number;
}
const isValidDownloadArgs = (args: any): args is DownloadWebsiteArgs =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.url === 'string' &&
  (args.outputPath === undefined || typeof args.outputPath === 'string') &&
  (args.depth === undefined || (typeof args.depth === 'number' && args.depth >= 0));
class WebsiteDownloaderServer {
  private server: Server;
  constructor() {
    this.server = new Server(
      {
        name: 'website-downloader',
        version: '0.1.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );
    this.setupToolHandlers();
    
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }
  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'download_website',
          description: 'Download an entire website using wget',
          inputSchema: {
            type: 'object',
            properties: {
              url: {
                type: 'string',
                description: 'URL of the website to download',
              },
              outputPath: {
                type: 'string',
                description: 'Path where the website should be downloaded (optional, defaults to current directory)',
              },
              depth: {
                type: 'number',
                description: 'Maximum depth level for recursive downloading (optional, defaults to infinite)',
                minimum: 0
              }
            },
            required: ['url'],
          },
        },
      ],
    }));
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name !== 'download_website') {
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Unknown tool: ${request.params.name}`
        );
      }
      if (!isValidDownloadArgs(request.params.arguments)) {
        throw new McpError(
          ErrorCode.InvalidParams,
          'Invalid download arguments'
        );
      }
      const { url, outputPath = process.cwd(), depth } = request.params.arguments;
      try {
        // Check if wget is installed
        await execAsync('which wget');
      } catch (error: any) {
        return {
          content: [
            {
              type: 'text',
              text: `Error downloading website: ${error.message || 'Unknown error'}`
            },
          ],
          isError: true,
        };
      }
      try {
        // Create wget command with options for downloading website
        const wgetCommand = [
          'wget',
          '--recursive',              // Download recursively
          `--level=${depth !== undefined ? depth : 'inf'}`,  // Recursion depth (infinite if not specified)
          '--page-requisites',       // Get all assets needed to display the page
          '--convert-links',         // Convert links to work locally
          '--adjust-extension',      // Add appropriate extensions to files
          '--span-hosts',            // Include necessary resources from other hosts
          '--domains=' + new URL(url).hostname,  // Restrict to same domain
          '--no-parent',             // Don't follow links to parent directory
          '--directory-prefix=' + outputPath,  // Output directory
          url
        ].join(' ');
        const { stdout, stderr } = await execAsync(wgetCommand);
        
        return {
          content: [
            {
              type: 'text',
              text: `Website downloaded successfully to ${outputPath}\n\nOutput:\n${stdout}\n${stderr}`,
            },
          ],
        };
      } catch (error: any) {
        return {
          content: [
            {
              type: 'text',
              text: `Error downloading website: ${error.message || 'Unknown error'}`,
            },
          ],
          isError: true,
        };
      }
    });
  }
  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Website Downloader MCP server running on stdio');
  }
}
const server = new WebsiteDownloaderServer();
server.run().catch(console.error);