#!/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`);
});