Skip to main content
Glama
index.ts10.4 kB
#!/usr/bin/env node /** * 这是一个模板 MCP 服务器,实现了一个简单的笔记系统。 * 它通过提供以下功能演示核心 MCP 概念: * - 将笔记列为资源 * - 阅读单个笔记 * - 通过工具创建新笔记 * - 通过提示总结所有笔记 */ import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { CMSBlogClient } from './cms-blog.js' /** * 笔记对象的类型别名。 */ type Note = { title: string; content: string } /** * 简单的内存笔记存储。 * 在真实实现中通常会使用数据库作为后端。 */ const notes: { [id: string]: Note } = { '1': { title: 'First Note', content: 'This is note 1' }, '2': { title: 'Second Note', content: 'This is note 2' }, } /** * CMS 博客客户端实例 */ const cmsApiUrl = process.env.CMS_BLOG_API_URL const cmsClient = cmsApiUrl ? new CMSBlogClient(cmsApiUrl) : new CMSBlogClient() /** * 创建一个 MCP 服务器,提供资源(列出/读取笔记)、工具(创建新笔记)和提示(总结笔记)等能力。 */ const server = new Server( { name: 'seo-blog', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, } ) /** * 用于将可用笔记列为资源的处理器。 * 每条笔记都会以以下形式暴露为资源: * - note:// URI 协议 * - 纯文本 MIME 类型 * - 包含笔记标题的人类可读名称和描述 */ server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: Object.entries(notes).map(([id, note]) => ({ uri: `note:///${id}`, mimeType: 'text/plain', name: note.title, description: `A text note: ${note.title}`, })), } }) /** * 读取指定笔记内容的处理器。 * 接收一个 note:// URI,并以纯文本形式返回笔记内容。 */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const url = new URL(request.params.uri) const id = url.pathname.replace(/^\//, '') const note = notes[id] if (!note) { throw new Error(`Note ${id} not found`) } return { contents: [ { uri: request.params.uri, mimeType: 'text/plain', text: note.content, }, ], } }) /** * 列出可用工具的处理器。 * 提供一个 "create_note" 工具,允许客户端创建新笔记。 */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'create_note', description: 'Create a new note', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the note', }, content: { type: 'string', description: 'Text content of the note', }, }, required: ['title', 'content'], }, }, { name: 'write_note', description: 'Write a new note', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the note', }, content: { type: 'string', description: 'Text content of the note', }, }, required: ['title', 'content'], }, }, { name: 'post_blog', description: 'Post a blog article to the CMS API using Zod validation', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the blog post', }, description: { type: 'string', description: 'Description of the blog post', }, content: { type: 'string', description: 'Content of the blog post', }, slug: { type: 'string', description: 'URL slug for the blog post', }, status: { type: 'string', description: 'Status of the blog post (draft), only draft is supported', enum: ['draft'], }, cover_url: { type: 'string', description: 'Cover image URL', }, author_name: { type: 'string', description: 'Author name', }, author_avatar_url: { type: 'string', description: 'Author avatar URL', }, locale: { type: 'string', description: 'Language locale', }, }, required: ['title', 'description', 'content', 'slug'], }, }, { name: 'validate_blog', description: 'Validate blog data using Zod schema without posting', inputSchema: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, content: { type: 'string' }, slug: { type: 'string' }, status: { type: 'string' }, cover_url: { type: 'string' }, author_name: { type: 'string' }, author_avatar_url: { type: 'string' }, locale: { type: 'string' }, }, required: ['title', 'description', 'content', 'slug'], }, }, ], } }) /** * "create_note" 工具的处理器。 * 使用提供的标题和内容创建新笔记,并返回成功信息。 */ server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'create_note': { const title = String(request.params.arguments?.title) const content = String(request.params.arguments?.content) if (!title || !content) { throw new Error('Title and content are required') } const id = String(Object.keys(notes).length + 1) notes[id] = { title, content } return { content: [ { type: 'text', text: `Created note ${id}: ${title}`, }, ], } } case 'write_note': { const title = String(request.params.arguments?.title) const content = String(request.params.arguments?.content) if (!title || !content) { throw new Error('Title and content are required') } const id = String(Object.keys(notes).length + 1) notes[id] = { title, content } return { content: [ { type: 'text', text: `Wrote note ${id}: ${title}`, }, ], } } case 'post_blog': { try { // 使用 CMS 客户端发布博客,内置 Zod 验证 const result = await cmsClient.postBlog(request.params.arguments || {}) return { content: [ { type: 'text', text: `Successfully posted blog!\nResponse: ${JSON.stringify(result, null, 2)}`, }, ], } } catch (error) { throw new Error(`Failed to post blog: ${error instanceof Error ? error.message : String(error)}`) } } case 'validate_blog': { try { // 验证博客数据但不发布 const validation = cmsClient.validateBlogData(request.params.arguments || {}) if (validation.success) { return { content: [ { type: 'text', text: `✅ Blog data validation successful!\nValidated data: ${JSON.stringify(validation.data, null, 2)}`, }, ], } } else { return { content: [ { type: 'text', text: `❌ Blog data validation failed!\nErrors: ${validation.errors?.join(', ')}`, }, ], } } } catch (error) { throw new Error(`Validation error: ${error instanceof Error ? error.message : String(error)}`) } } default: throw new Error('Unknown tool') } }) /** * 列出可用提示的处理器。 * 提供一个 "summarize_notes" 提示,用于总结所有笔记。 */ server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: 'summarize_notes', description: 'Summarize all notes', }, { name: 'post_blog', description: 'use this prompt when publishing a cms blog', }, ], } }) /** * "summarize_notes" 提示的处理器。 * 返回一个请求总结所有笔记的提示,并将笔记内容作为资源嵌入。 */ server.setRequestHandler(GetPromptRequestSchema, async (request) => { if (request.params.name !== 'summarize_notes') { throw new Error('Unknown prompt') } const embeddedNotes = Object.entries(notes).map(([id, note]) => ({ type: 'resource' as const, resource: { uri: `note:///${id}`, mimeType: 'text/plain', text: note.content, }, })) return { messages: [ { role: 'user', content: { type: 'text', text: 'Please summarize the following notes:', }, }, ...embeddedNotes.map((note) => ({ role: 'user' as const, content: note, })), { role: 'user', content: { type: 'text', text: 'Provide a concise summary of all the notes above.', }, }, { role: 'user', content: { type: 'text', text: 'Use the post_blog tool to publish the blog', }, }, ], } }) /** * 使用 stdio 传输启动服务器。 * 这使服务器能够通过标准输入/输出流进行通信。 */ async function main() { const transport = new StdioServerTransport() await server.connect(transport) } main().catch((error) => { console.error('Server error:', 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/La-fe/seo-mcp'

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