Skip to main content
Glama

Raindrop.io MCP Server

server.test.ts8.77 kB
import { describe, expect, it, jest } from "@jest/globals"; import { CallToolRequest } from "@modelcontextprotocol/sdk/types.js"; import { RaindropAPI } from "../lib/raindrop-api.js"; import { CreateBookmarkSchema, SearchBookmarksSchema } from "../types/index.js"; // モックのfetch関数 const mockFetch = jest.fn().mockImplementation(() => Promise.resolve({ ok: true, json: () => Promise.resolve({}), }), ); global.fetch = mockFetch as unknown as typeof fetch; describe("Tool Handlers", () => { const api = new RaindropAPI("test-token"); beforeEach(() => { mockFetch.mockClear(); }); describe("create-bookmark", () => { const handler = async (request: CallToolRequest) => { const { name, arguments: args } = request.params; if (name !== "create-bookmark") throw new Error("Invalid tool"); const { url, title, tags, collection } = CreateBookmarkSchema.parse(args); const bookmark = await api.createBookmark({ link: url, title, tags, collection: { $id: collection || 0 }, }); return { content: [ { type: "text", text: `Bookmark created successfully: ${bookmark.item?.link || url}`, }, ], }; }; it("should create a bookmark with required fields", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ item: { link: "https://example.com" } }), }), ); const result = await handler({ method: "tools/call", params: { name: "create-bookmark", arguments: { url: "https://example.com", }, }, }); expect(result).toEqual({ content: [ { type: "text", text: "Bookmark created successfully: https://example.com", }, ], }); }); it("should handle validation errors", async () => { await expect( handler({ method: "tools/call", params: { name: "create-bookmark", arguments: { url: "invalid-url", }, }, }), ).rejects.toThrow("Invalid"); }); }); describe("search-bookmarks", () => { const handler = async (request: CallToolRequest) => { const { name, arguments: args } = request.params; if (name !== "search-bookmarks") throw new Error("Invalid tool"); const { query, tags, page, perpage, sort, collection, word } = SearchBookmarksSchema.parse(args); const searchParams = new URLSearchParams({ search: query, ...(tags && { tags: tags.join(",") }), ...(page !== undefined && { page: page.toString() }), ...(perpage !== undefined && { perpage: perpage.toString() }), ...(sort && { sort }), ...(word !== undefined && { word: word.toString() }), }); const collectionId = collection ?? 0; const results = await api.searchBookmarks(collectionId, searchParams); return { content: [ { type: "text", text: results.items.length > 0 ? `Found ${results.count} total bookmarks (showing ${ results.items.length } on page ${page ?? 0 + 1}):\n${results.items .map( (item) => ` Title: ${item.title} URL: ${item.link} Tags: ${item.tags?.length ? item.tags.join(", ") : "No tags"} Created: ${new Date(item.created).toLocaleString()} Last Updated: ${new Date(item.lastUpdate).toLocaleString()} ---`, ) .join("\n")}` : "No bookmarks found matching your search.", }, ], }; }; const mockSearchResults = { items: [ { title: "Test Bookmark", link: "https://example.com", tags: ["test", "example"], created: "2024-03-20T00:00:00.000Z", lastUpdate: "2024-03-20T00:00:00.000Z", }, ], count: 1, }; it("should search bookmarks with query", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockSearchResults), }), ); const result = await handler({ method: "tools/call", params: { name: "search-bookmarks", arguments: { query: "test", }, }, }); expect(result.content[0].text).toContain("Found 1 total bookmarks"); expect(result.content[0].text).toContain("Test Bookmark"); }); it("should handle pagination", async () => { const paginatedResults = { ...mockSearchResults, count: 30, items: Array(10).fill(mockSearchResults.items[0]), }; mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve(paginatedResults), }), ); const result = await handler({ method: "tools/call", params: { name: "search-bookmarks", arguments: { query: "test", page: 1, perpage: 10, }, }, }); expect(result.content[0].text).toContain("Found 30 total bookmarks"); expect(result.content[0].text).toContain("showing 10"); }); it("should apply sorting", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockSearchResults), }), ); await handler({ method: "tools/call", params: { name: "search-bookmarks", arguments: { query: "test", sort: "-created", }, }, }); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining("sort=-created"), expect.any(Object), ); }); it("should filter by tags", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockSearchResults), }), ); await handler({ method: "tools/call", params: { name: "search-bookmarks", arguments: { query: "test", tags: ["example", "test"], }, }, }); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining("tags=example%2Ctest"), expect.any(Object), ); }); }); describe("list-collections", () => { const handler = async (request: CallToolRequest) => { const { name } = request.params; if (name !== "list-collections") throw new Error("Invalid tool"); const collections = await api.listCollections(); return { content: [ { type: "text", text: collections.items.length > 0 ? `Found ${collections.items.length} collections:\n${collections.items .map( (item) => ` Name: ${item.title} ID: ${item._id} Count: ${item.count} bookmarks Parent: ${item.parent?._id || "None"} Created: ${new Date(item.created).toLocaleString()} ---`, ) .join("\n")}` : "No collections found.", }, ], }; }; const mockCollections = { items: [ { title: "Test Collection", _id: 1, count: 5, created: "2024-03-20T00:00:00.000Z", }, ], }; it("should list all collections", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockCollections), }), ); const result = await handler({ method: "tools/call", params: { name: "list-collections", arguments: {}, }, }); expect(result.content[0].text).toContain("Found 1 collections"); expect(result.content[0].text).toContain("Test Collection"); }); it("should handle empty collections", async () => { mockFetch.mockImplementationOnce(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ items: [] }), }), ); const result = await handler({ method: "tools/call", params: { name: "list-collections", arguments: {}, }, }); expect(result.content[0].text).toBe("No collections found."); }); }); });

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/hiromitsusasaki/raindrop-io-mcp-server'

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