index.ts•9.16 kB
#!/usr/bin/env node
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import rssManager from './rss-manager';
import dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
const app = new Hono();
// Status endpoint
app.get('/status', (c) => {
  return c.json({ status: 'ok', service: 'mcp-rss-manager' });
});
// MCP endpoint
app.post('/mcp', async (c) => {
  try {
    const request = await c.req.json();
    
    // Process the request using the MCP server
    // Since we don't have direct HTTP handling in the MCP SDK,
    // we'll manually process the request and format the response
    
    // The request should contain a method and params
    const { method, params } = request;
    
    let response;
    
    switch (method) {
      case 'fetchRssFeeds':
        const { limit } = params || { limit: 10 };
        const feeds = await rssManager.crawlFeed(limit);
        response = { result: feeds };
        break;
        
      case 'fetchArticle':
        if (!params?.url) {
          response = { error: { code: -32602, message: 'Invalid params: url is required' } };
        } else {
          const article = await rssManager.fetchArticleFromUrl(params.url);
          response = { result: article };
        }
        break;
        
      case 'crawlWebsite':
        if (!params?.url) {
          response = { error: { code: -32602, message: 'Invalid params: url is required' } };
        } else {
          const result = await rssManager.crawlWebsite(params.url, params.limit || 100);
          response = { result };
        }
        break;
        
      case 'asyncCrawlWebsite':
        if (!params?.url) {
          response = { error: { code: -32602, message: 'Invalid params: url is required' } };
        } else {
          const result = await rssManager.asyncCrawlWebsite(params.url, params.limit || 100);
          response = { result };
        }
        break;
        
      case 'checkCrawlStatus':
        if (!params?.id) {
          response = { error: { code: -32602, message: 'Invalid params: id is required' } };
        } else {
          const result = await rssManager.checkCrawlStatus(params.id);
          response = { result };
        }
        break;
        
      case 'cancelCrawl':
        if (!params?.id) {
          response = { error: { code: -32602, message: 'Invalid params: id is required' } };
        } else {
          const result = await rssManager.cancelCrawl(params.id);
          response = { result };
        }
        break;
        
      default:
        response = { error: { code: -32601, message: 'Method not found' } };
    }
    
    return c.json(response);
  } catch (error) {
    console.error('Error processing MCP request:', error);
    return c.json({ 
      error: { 
        code: -32603, 
        message: 'Internal error',
        data: error instanceof Error ? error.message : String(error)
      }
    }, 500);
  }
});
// API endpoint to get latest feeds
app.get('/api/feeds', async (c) => {
  try {
    // Get the limit parameter from the query string, default to 10
    const limitParam = c.req.query('limit');
    const limit = limitParam ? parseInt(limitParam) : 10;
    
    // Get the latest feeds from RSS Manager
    const feeds = await rssManager.getLatestArticles(limit);
    
    return c.json({ 
      status: 'success',
      count: feeds.items.length,
      feeds
    });
  } catch (error: any) {
    console.error('Error fetching feeds:', error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to get feeds by category
app.get('/api/feeds/category/:category', async (c) => {
  try {
    const category = c.req.param('category');
    const limitParam = c.req.query('limit');
    const limit = limitParam ? parseInt(limitParam) : 10;
    
    const feeds = await rssManager.getFeedsByCategory(category, limit);
    
    return c.json({ 
      status: 'success',
      category,
      count: feeds.items.length,
      feeds
    });
  } catch (error: any) {
    console.error(`Error fetching feeds for category ${c.req.param('category')}:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to search feeds
app.get('/api/feeds/search', async (c) => {
  try {
    const query = c.req.query('q');
    if (!query) {
      return c.json({ 
        status: 'error',
        message: 'Search query is required'
      }, 400);
    }
    
    const limitParam = c.req.query('limit');
    const limit = limitParam ? parseInt(limitParam) : 10;
    
    const feeds = await rssManager.searchFeeds(query, limit);
    
    return c.json({ 
      status: 'success',
      query,
      count: feeds.items.length,
      feeds
    });
  } catch (error: any) {
    console.error(`Error searching feeds:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to fetch an article from a URL
app.post('/api/articles/fetch', async (c) => {
  try {
    const { url } = await c.req.json();
    if (!url) {
      return c.json({ 
        status: 'error',
        message: 'URL is required'
      }, 400);
    }
    
    const article = await rssManager.fetchArticleFromUrl(url);
    
    if (!article) {
      return c.json({ 
        status: 'error',
        message: 'Failed to fetch article'
      }, 500);
    }
    
    return c.json({ 
      status: 'success',
      article
    });
  } catch (error: any) {
    console.error(`Error fetching article:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to crawl a website
app.post('/api/crawl', async (c) => {
  try {
    const { url, limit } = await c.req.json();
    if (!url) {
      return c.json({ 
        status: 'error',
        message: 'URL is required'
      }, 400);
    }
    
    const result = await rssManager.crawlWebsite(url, limit || 100);
    
    return c.json({ 
      status: result.success ? 'success' : 'error',
      ...result
    });
  } catch (error: any) {
    console.error(`Error crawling website:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to start an asynchronous crawl
app.post('/api/crawl/async', async (c) => {
  try {
    const { url, limit } = await c.req.json();
    if (!url) {
      return c.json({ 
        status: 'error',
        message: 'URL is required'
      }, 400);
    }
    
    const result = await rssManager.asyncCrawlWebsite(url, limit || 100);
    
    return c.json({ 
      status: result.success ? 'success' : 'error',
      ...result
    });
  } catch (error: any) {
    console.error(`Error starting async crawl:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to check crawl status
app.get('/api/crawl/status/:id', async (c) => {
  try {
    const id = c.req.param('id');
    if (!id) {
      return c.json({ 
        status: 'error',
        message: 'Crawl ID is required'
      }, 400);
    }
    
    const result = await rssManager.checkCrawlStatus(id);
    
    return c.json({ 
      status: result.success ? 'success' : 'error',
      ...result
    });
  } catch (error: any) {
    console.error(`Error checking crawl status:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to cancel a crawl
app.post('/api/crawl/cancel/:id', async (c) => {
  try {
    const id = c.req.param('id');
    if (!id) {
      return c.json({ 
        status: 'error',
        message: 'Crawl ID is required'
      }, 400);
    }
    
    const result = await rssManager.cancelCrawl(id);
    
    return c.json({ 
      status: result.success ? 'success' : 'error',
      ...result
    });
  } catch (error: any) {
    console.error(`Error cancelling crawl:`, error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// API endpoint to list all configured feeds
app.get('/api/feeds/list', async (c) => {
  try {
    const feeds = await rssManager.getFeeds();
    
    return c.json({ 
      status: 'success',
      count: feeds.length,
      feeds
    });
  } catch (error: any) {
    console.error('Error listing feeds:', error);
    return c.json({ 
      status: 'error',
      message: error.message,
      error: error.toString()
    }, 500);
  }
});
// Start the server
serve({
  fetch: app.fetch,
  port: process.env.PORT ? parseInt(process.env.PORT) : 5556, 
}, (info) => {
  console.error(`RSS Manager server is running at http://localhost:${info.port}`);
  console.error(`MCP endpoint available at http://localhost:${info.port}/mcp`);
  console.error(`API endpoints available at http://localhost:${info.port}/api/feeds`);
});