mcp-server.ts•20.9 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import rssManager from './rss-manager';
// MCPサーバーインスタンスの作成
export const server = new McpServer({
  name: "RssFeedMCP",
  version: "0.1.0",
});
// RSS Managerからフィードを取得するツール
server.tool(
  "fetchRssFeeds",
  "Fetch articles from configured RSS feeds",
  { limit: z.number().min(1).max(50).default(10).describe("Number of articles to retrieve") },
  async ({ limit }) => {
    try {
      // RSS Managerからフィードを取得
      const feeds = await rssManager.crawlFeed(limit);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(feeds, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error fetching RSS feeds:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error fetching RSS feeds: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSS Managerから最新のフィードを取得するツール
server.tool(
  "getLatestRssFeeds",
  "Get the latest articles from configured RSS feeds",
  { limit: z.number().min(1).max(50).default(10).describe("Number of articles to retrieve") },
  async ({ limit }) => {
    try {
      // RSS Managerから最新のフィードを取得
      const feeds = await rssManager.getLatestArticles(limit);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(feeds, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error retrieving latest RSS feeds:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error retrieving latest RSS feeds: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSS Managerからカテゴリ別のフィードを取得するツール
server.tool(
  "fetchRssFeedsByCategory",
  "Fetch articles from configured RSS feeds by category",
  { 
    category: z.string().describe("Category name to filter feeds by"),
    limit: z.number().min(1).max(50).default(10).describe("Number of articles to retrieve") 
  },
  async ({ category, limit }) => {
    try {
      // RSS Managerからカテゴリ別のフィードを取得
      const feeds = await rssManager.getFeedsByCategory(category, limit);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(feeds, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error fetching RSS feeds by category:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error fetching RSS feeds by category: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSS Managerでフィードを検索するツール
server.tool(
  "searchRssFeeds",
  "Search for articles in configured RSS feeds",
  { 
    query: z.string().describe("Search query"),
    limit: z.number().min(1).max(50).default(10).describe("Number of articles to retrieve") 
  },
  async ({ query, limit }) => {
    try {
      // RSS Managerでフィードを検索
      const feeds = await rssManager.searchFeeds(query, limit);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(feeds, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error searching RSS feeds:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error searching RSS feeds: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSSフィードの一覧を取得するツール
server.tool(
  "listRssFeeds",
  "Get a list of all configured RSS feeds",
  {},
  async () => {
    try {
      // RSS Managerからフィード一覧を取得
      const feeds = await rssManager.getFeeds();
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(feeds, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error listing RSS feeds:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error listing RSS feeds: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSSフィードを追加するツール
server.tool(
  "addRssFeed",
  "Add a new RSS feed to the database",
  { 
    url: z.string().url().describe("URL of the RSS feed"),
    name: z.string().describe("Name of the RSS feed"),
    category: z.string().optional().describe("Category of the RSS feed (optional)") 
  },
  async ({ url, name, category }) => {
    try {
      // RSS Managerにフィードを追加
      const success = await rssManager.addFeed({ url, name, category });
      
      if (success) {
        return {
          content: [
            {
              type: "text",
              text: `Successfully added RSS feed: ${name} (${url})`,
            },
          ],
        };
      } else {
        return {
          content: [
            {
              type: "text",
              text: `Failed to add RSS feed: ${name} (${url}). The feed may already exist.`,
            },
          ],
          isError: true
        };
      }
    } catch (error: any) {
      console.error("Error adding RSS feed:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error adding RSS feed: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// RSSフィードを削除するツール
server.tool(
  "removeRssFeed",
  "Remove an RSS feed from the database",
  { 
    url: z.string().url().describe("URL of the RSS feed to remove")
  },
  async ({ url }) => {
    try {
      // RSS Managerからフィードを削除
      const success = await rssManager.removeFeed(url);
      
      if (success) {
        return {
          content: [
            {
              type: "text",
              text: `Successfully removed RSS feed: ${url}`,
            },
          ],
        };
      } else {
        return {
          content: [
            {
              type: "text",
              text: `Failed to remove RSS feed: ${url}. The feed may not exist.`,
            },
          ],
          isError: true
        };
      }
    } catch (error: any) {
      console.error("Error removing RSS feed:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error removing RSS feed: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// ユーザーの興味キーワード一覧を取得するツール
server.tool(
  "listKeywords",
  "Get a list of all user interest keywords",
  {},
  async () => {
    try {
      // RSS Managerからキーワード一覧を取得
      const keywords = await rssManager.getKeywords();
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(keywords, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error listing keywords:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error listing keywords: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// ユーザーの興味キーワードを追加するツール
server.tool(
  "addKeyword",
  "Add a new interest keyword to the database",
  { 
    keyword: z.string().min(1).describe("Interest keyword to add")
  },
  async ({ keyword }) => {
    try {
      // RSS Managerにキーワードを追加
      const success = await rssManager.addKeyword(keyword);
      
      if (success) {
        return {
          content: [
            {
              type: "text",
              text: `Successfully added interest keyword: "${keyword}"`,
            },
          ],
        };
      } else {
        return {
          content: [
            {
              type: "text",
              text: `Failed to add interest keyword: "${keyword}". The keyword may already exist.`,
            },
          ],
          isError: true
        };
      }
    } catch (error: any) {
      console.error("Error adding keyword:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error adding keyword: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// ユーザーの興味キーワードを削除するツール
server.tool(
  "removeKeyword",
  "Remove an interest keyword from the database",
  { 
    keyword: z.string().min(1).describe("Interest keyword to remove")
  },
  async ({ keyword }) => {
    try {
      // RSS Managerからキーワードを削除
      const success = await rssManager.removeKeyword(keyword);
      
      if (success) {
        return {
          content: [
            {
              type: "text",
              text: `Successfully removed interest keyword: "${keyword}"`,
            },
          ],
        };
      } else {
        return {
          content: [
            {
              type: "text",
              text: `Failed to remove interest keyword: "${keyword}". The keyword may not exist.`,
            },
          ],
          isError: true
        };
      }
    } catch (error: any) {
      console.error("Error removing keyword:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error removing keyword: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// ユーザーの興味キーワードに一致する記事を取得するツール
server.tool(
  "getArticlesByKeywords",
  "Get articles matching user interest keywords",
  { 
    limit: z.number().min(1).max(50).default(10).describe("Number of articles to retrieve")
  },
  async ({ limit }) => {
    try {
      // RSS Managerからキーワードに一致する記事を取得
      const articles = await rssManager.getArticlesByKeywords(limit);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(articles, null, 2),
          },
        ],
      };
    } catch (error: any) {
      console.error("Error getting articles by keywords:", error.message);
      return {
        content: [
          {
            type: "text",
            text: `Error getting articles by keywords: ${error.message}`,
          },
        ],
        isError: true
      };
    }
  }
);
// Add a tool to fetch articles from a specified URL using firecrawl
server.tool(
  "fetchArticle",
  "Fetch an article from a specified URL using firecrawl",
  {
    url: z.string().url().describe("URL of the article to fetch")
  },
  async ({ url }) => {
    try {
      console.error(`Fetching article from URL: ${url}`);
      
      // Use the RSS manager to fetch the article
      const article = await rssManager.fetchArticleFromUrl(url);
      
      if (!article) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to fetch article from the URL."
            }
          ],
          isError: true
        };
      }
      
      // Format the article content for the response
      return {
        content: [
          {
            type: "text",
            text: `# ${article.title || 'Article'}\n\n${article.content || ''}`
          }
        ]
      };
    } catch (error) {
      console.error(`Error in fetchArticle tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error fetching article: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to get all articles from the database
server.tool(
  "getArticles",
  "Get all articles from the database",
  {
    limit: z.number().optional().describe("Maximum number of articles to return (default: 10)")
  },
  async ({ limit = 10 }) => {
    try {
      console.error(`Getting articles with limit: ${limit}`);
      
      // Use the RSS manager to get articles
      const articles = await rssManager.getArticles(limit);
      
      if (!articles || articles.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: "No articles found in the database."
            }
          ]
        };
      }
      
      // Format the articles for the response
      let responseText = "# Articles\n\n";
      
      articles.forEach((article, index) => {
        responseText += `## ${index + 1}. ${article.title || 'Untitled Article'}\n`;
        responseText += `- URL: ${article.url}\n`;
        if (article.author) responseText += `- Author: ${article.author}\n`;
        if (article.published_date) responseText += `- Published: ${article.published_date}\n`;
        if (article.summary) responseText += `\n${article.summary}\n\n`;
        responseText += `---\n\n`;
      });
      
      return {
        content: [
          {
            type: "text",
            text: responseText
          }
        ]
      };
    } catch (error) {
      console.error(`Error in getArticles tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error getting articles: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to search articles in the database
server.tool(
  "searchArticles",
  "Search articles in the database",
  {
    query: z.string().describe("Search query"),
    limit: z.number().optional().describe("Maximum number of articles to return (default: 10)")
  },
  async ({ query, limit = 10 }) => {
    try {
      console.error(`Searching articles with query: ${query}, limit: ${limit}`);
      
      // Use the RSS manager to search articles
      const articles = await rssManager.searchArticles(query, limit);
      
      if (!articles || articles.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No articles found matching the query: "${query}"`
            }
          ]
        };
      }
      
      // Format the articles for the response
      let responseText = `# Search Results for "${query}"\n\n`;
      
      articles.forEach((article, index) => {
        responseText += `## ${index + 1}. ${article.title || 'Untitled Article'}\n`;
        responseText += `- URL: ${article.url}\n`;
        if (article.author) responseText += `- Author: ${article.author}\n`;
        if (article.published_date) responseText += `- Published: ${article.published_date}\n`;
        if (article.summary) responseText += `\n${article.summary}\n\n`;
        responseText += `---\n\n`;
      });
      
      return {
        content: [
          {
            type: "text",
            text: responseText
          }
        ]
      };
    } catch (error) {
      console.error(`Error in searchArticles tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error searching articles: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to crawl a website using Firecrawl
server.tool(
  "crawlWebsite",
  "Crawl a website and save all pages to the database",
  {
    url: z.string().url().describe("URL of the website to crawl"),
    limit: z.number().min(1).max(500).optional().describe("Maximum number of pages to crawl (default: 100)")
  },
  async ({ url, limit = 100 }) => {
    try {
      console.error(`Crawling website: ${url} with limit: ${limit}`);
      
      // Use the RSS manager to crawl the website
      const result = await rssManager.crawlWebsite(url, limit);
      
      if (!result.success) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to crawl website: ${result.error || "Unknown error"}`
            }
          ],
          isError: true
        };
      }
      
      // Format the result for the response
      return {
        content: [
          {
            type: "text",
            text: `Successfully crawled website: ${url}\n\nTotal pages: ${result.totalPages}\nSaved pages: ${result.savedPages}`
          }
        ]
      };
    } catch (error) {
      console.error(`Error in crawlWebsite tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error crawling website: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to asynchronously crawl a website using Firecrawl
server.tool(
  "asyncCrawlWebsite",
  "Start an asynchronous crawl of a website",
  {
    url: z.string().url().describe("URL of the website to crawl"),
    limit: z.number().min(1).max(500).optional().describe("Maximum number of pages to crawl (default: 100)")
  },
  async ({ url, limit = 100 }) => {
    try {
      console.error(`Starting async crawl of website: ${url} with limit: ${limit}`);
      
      // Use the RSS manager to start an async crawl
      const result = await rssManager.asyncCrawlWebsite(url, limit);
      
      if (!result.success) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to start async crawl: ${result.error || "Unknown error"}`
            }
          ],
          isError: true
        };
      }
      
      // Format the result for the response
      return {
        content: [
          {
            type: "text",
            text: `Successfully started async crawl of website: ${url}\n\nCrawl ID: ${result.id}\nStatus: ${result.status}\n\nYou can check the status of this crawl using the checkCrawlStatus tool with the ID.`
          }
        ]
      };
    } catch (error) {
      console.error(`Error in asyncCrawlWebsite tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error starting async crawl: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to check the status of an asynchronous crawl
server.tool(
  "checkCrawlStatus",
  "Check the status of an asynchronous crawl",
  {
    id: z.string().describe("ID of the crawl job to check")
  },
  async ({ id }) => {
    try {
      console.error(`Checking status of crawl: ${id}`);
      
      // Use the RSS manager to check the crawl status
      const result = await rssManager.checkCrawlStatus(id);
      
      if (!result.success) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to check crawl status: ${result.error || "Unknown error"}`
            }
          ],
          isError: true
        };
      }
      
      // Format the result for the response
      return {
        content: [
          {
            type: "text",
            text: `Crawl Status for ID: ${id}\n\nStatus: ${result.status}\nPages Processed: ${result.pagesProcessed}\nTotal Pages: ${result.totalPages}`
          }
        ]
      };
    } catch (error) {
      console.error(`Error in checkCrawlStatus tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error checking crawl status: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);
// Add a tool to cancel an asynchronous crawl
server.tool(
  "cancelCrawl",
  "Cancel an asynchronous crawl job",
  {
    id: z.string().describe("ID of the crawl job to cancel")
  },
  async ({ id }) => {
    try {
      console.error(`Cancelling crawl: ${id}`);
      
      // Use the RSS manager to cancel the crawl
      const result = await rssManager.cancelCrawl(id);
      
      if (!result.success) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to cancel crawl: ${result.error || "Unknown error"}`
            }
          ],
          isError: true
        };
      }
      
      // Format the result for the response
      return {
        content: [
          {
            type: "text",
            text: `Successfully cancelled crawl: ${id}`
          }
        ]
      };
    } catch (error) {
      console.error(`Error in cancelCrawl tool: ${error}`);
      return {
        content: [
          {
            type: "text",
            text: `Error cancelling crawl: ${error}`
          }
        ],
        isError: true
      };
    }
  }
);