Skip to main content
Glama
index.ts6.92 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { TOOL } from "./types.js"; import { getBookReviews, getGroupTopicDetail, getGroupTopics, getMovieReviews, getTVReviews, searchBooks, searchMovies } from "./api.js"; import json2md from 'json2md' import open from 'open' import dayjs from "dayjs"; import TurndownService from "turndown"; import { z } from "zod"; const server = new McpServer( { name: "L-Chris/douban-mcp", version: "0.2.0", } ); // 搜索图书 server.tool( TOOL.SEARCH_BOOK, 'search books from douban, either by ISBN or by query', { q: z.string().optional().describe('query string, e.g. "python"'), isbn: z.string().optional().describe('ISBN number, e.g. "9787501524044"') }, async (args) => { if (!args.isbn && !args.q) { throw new McpError(ErrorCode.InvalidParams, "Either q or isbn must be provided") } const books = await searchBooks(args) const text = json2md({ table: { headers: ['publish_date', 'title', 'author', 'rating' ,'id', 'isbn'], rows: books.map(_ => ({ id: _.id || '', title: _.title || '', author: (_.author || []).join('、'), publish_date: _.pubdate, isbn: _.isbn13 || '', rating: `${_.rating?.average || 0} (${_.rating?.numRaters || 0}人)` })) } }) return { content: [{ type: 'text', text }] } } ); // 获取图书长评列表 server.tool( TOOL.LIST_BOOK_REVIEWS, "list book reviews", { id: z.string().describe('douban book id, e.g. "1234567890"') }, async (args) => { if (!args.id) { throw new McpError(ErrorCode.InvalidParams, "douban book id must be provided") } const reviews = await getBookReviews({ id: args.id }) const text = json2md({ table: { headers: ['title', 'rating', 'summary', 'id'], rows: reviews.reviews.map(_ => ({ id: _.id, title: _.title, rating: `${_.rating?.value || 0} (${_.rating?.count || 0}人)`, summary: _.abstract })) } }) return { content: [{ type: 'text', text }] } } ) // 搜索电影或电视剧 server.tool( TOOL.SEARCH_MOVIE, 'search movies or tvs from douban by query', { q: z.string().describe('query string, e.g. "python"') }, async (args) => { if (!args.q) { throw new McpError(ErrorCode.InvalidParams, "q must be provided") } const movies = await searchMovies(args) const text = json2md({ table: { headers: ['title', 'subtitle', 'publish_date', 'rating', 'id'], rows: movies.map(_ => ({ id: _.id, title: _.title, subtitle: _.card_subtitle, publish_date: _.year, rating: `${_.rating?.value || '0'} (${_.rating?.count || 0}人)`, })) } }) return { content: [{ type: 'text', text }] } } ); // 获取电影长评列表 server.tool( TOOL.LIST_MOVIE_REVIEWS, "list movie reviews", { id: z.string().describe('douban movie id, e.g. "1234567890"') }, async (args) => { if (!args.id) { throw new McpError(ErrorCode.InvalidParams, "douban movie id must be provided") } const reviews = await getMovieReviews({ id: args.id }) const text = json2md({ table: { headers: ['title', 'rating', 'summary', 'id'], rows: reviews.map(_ => ({ id: _.id, title: _.title, rating: `${_.rating?.value || 0} (有用:${_.useful_count || 0}人)`, summary: _.abstract })) } }) return { content: [{ type: "text", text }] } } ) // 获取电视剧长评列表 server.tool( 'list-tv-reviews', "list tv reviews", { id: z.string().describe('douban tv id, e.g. "1234567890"') }, async (args) => { if (!args.id) { throw new McpError(ErrorCode.InvalidParams, "douban tv id must be provided") } const reviews = await getTVReviews({ id: args.id }) const text = json2md({ table: { headers: ['title', 'rating', 'summary', 'id'], rows: reviews.map(_ => ({ id: _.id, title: _.title, rating: `${_.rating?.value || 0} (有用:${_.useful_count || 0}人)`, summary: _.abstract })) } }) return { content: [{ type: "text", text }] } } ) // 浏览图书详情 server.tool( TOOL.BROWSE, "open default browser and browse douban book detail", { id: z.string().describe('douban book id, e.g. "1234567890"') }, async (args) => { if (!args.id) { throw new McpError(ErrorCode.InvalidParams, "douban book id must be provided") } await open(`https://book.douban.com/subject/${args.id}/`); return { content: [{ type: "text", text: `The Douban Book Page has been opened in your default browser`, }] } } ); // 获取小组话题列表 server.tool( TOOL.LIST_GROUP_TOPICS, "list group topics", { id: z.string().optional().describe('douban group id'), tags: z.array(z.string()).optional().describe('tags, e.g. ["python"]'), from_date: z.string().optional().describe('from date, e.g. "2024-01-01"') }, async (args) => { const id = args.id || '732764' const topics = await getGroupTopics({ id, tags: args.tags, from_date: args.from_date }) const text = json2md({ table: { headers: ['publish_date', 'tags', 'title', 'id'], rows: topics.map(_ => ({ id: _.id, tags: _.topic_tags.map(_ => _.name).join('、'), title: `[${_.title}](${_.url})`, publish_date: dayjs(_.create_time).format('YYYY/MM/DD'), })) } }) return { content: [{ type: "text", text }] } } ); // 获取小组话题详情 server.tool( TOOL.GET_GROUP_TOPIC_DETAIL, "get group topic detail", { id: z.string().describe('douban group topic id, e.g. "1234567890"') }, async (args) => { if (!args.id) { throw new McpError(ErrorCode.InvalidParams, "douban group topic id must be provided") } const topic = await getGroupTopicDetail({ id: args.id }) if (!topic?.id) throw new McpError(ErrorCode.InvalidRequest, "request failed") const tService = new TurndownService() const text = `title: ${topic.title} tags: ${topic.topic_tags.map(_ => _.name).join('|')} content: ${tService.turndown(topic.content)} ` return { content: [{ type: "text", text }] } } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

Implementation Reference

Latest Blog Posts

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/moria97/douban-mcp'

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