Skip to main content

搭建 Notes MCP 服务器

🌐 Building a Notes MCP Server

在本指南中,你将学习如何从零开始构建一个完整的 MCP(模型上下文协议)服务器。该服务器将管理一组 Markdown 注意,并具有以下功能:

🌐 In this guide, you'll learn how to build a complete MCP (Model Context Protocol) server from scratch. This server will manage a collection of markdown notes and has these features:

  1. 列出和阅读注意:允许客户端浏览并查看存储在服务器上的 Markdown 文件
  2. 写注意:提供一个用于创建或更新注意的工具
  3. 提供智能提示:生成上下文相关的提示,例如创建每日注意模板或总结现有内容

先决条件
Direct link to 先决条件

🌐 Prerequisites

  • 已安装 Node.js v22.13.0 或更高版本
  • 来自支持的模型提供商的 API 密钥
  • 一个现有的 Mastra 项目(按照安装指南设置新项目)

添加必要的依赖和文件
Direct link to 添加必要的依赖和文件

🌐 Adding necessary dependencies & files

在创建 MCP 服务器之前,你首先需要安装额外的依赖并设置一个基础文件夹结构。

🌐 Before you can create an MCP server you first need to install additional dependencies and set up a boilerplate folder structure.

  1. @mastra/mcp 添加到你的项目中:

    🌐 Add @mastra/mcp to your project:

    npm install @mastra/mcp@latest
  2. 按照默认的安装指南操作后,你的项目将包含与本指南无关的文件。你可以安全地将它们删除:

    🌐 After following the default installation guide your project will include files that are not relevant for this guide. You can safely remove them:

    rm -rf src/mastra/agents src/mastra/workflows src/mastra/tools/weather-tool.ts

    你还应该像这样更改 src/mastra/index.ts 文件:

    🌐 You should also change the src/mastra/index.ts file like so:

    src/mastra/index.ts
    import { Mastra } from "@mastra/core";
    import { PinoLogger } from "@mastra/loggers";
    import { LibSQLStore } from "@mastra/libsql";

    export const mastra = new Mastra({
    storage: new LibSQLStore({
    id: 'mastra-storage',
    // stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
    }),
    logger: new PinoLogger({
    name: "Mastra",
    level: "info",
    }),
    });
  3. 为你的 MCP 服务器逻辑创建一个专用目录,并为你的注意创建一个 notes 目录:

    🌐 Create a dedicated directory for your MCP server's logic and a notes directory for your notes:

    mkdir notes src/mastra/mcp

    创建以下文件:

    🌐 Create the following files:

    touch src/mastra/mcp/{server,resources,prompts}.ts
    • server.ts:将包含主要的 MCP 服务器配置
    • resources.ts:将处理列出和读取注意文件
    • prompts.ts:将包含智能提示的逻辑

    生成的目录结构应如下所示:

    🌐 The resulting directory structure should look like this:

    <your-project-name>/
    ├── notes/
    └── src/
    └── mastra/
    ├── index.ts
    ├── mcp/
    │ ├── server.ts
    │ ├── resources.ts
    │ └── prompts.ts
    └── tools/

创建 MCP 服务器
Direct link to 创建 MCP 服务器

🌐 Creating the MCP Server

让我们添加 MCP 服务器!

🌐 Let's add the MCP server!

  1. src/mastra/mcp/server.ts 中,定义 MCP 服务器实例:

    🌐 In src/mastra/mcp/server.ts, define the MCP server instance:

    src/mastra/mcp/server.ts
    import { MCPServer } from "@mastra/mcp";

    export const notes = new MCPServer({
    id: "notes",
    name: "Notes Server",
    version: "0.1.0",
    tools: {},
    });

    在你的 Mastra 实例 src/mastra/index.ts 中注册此 MCP 服务器。密钥 notes 是你的 MCP 服务器的公用标识符:

    🌐 Register this MCP server in your Mastra instance at src/mastra/index.ts. The key notes is the public identifier for your MCP server:

    src/mastra/index.ts
    import { Mastra } from "@mastra/core";
    import { PinoLogger } from "@mastra/loggers";
    import { LibSQLStore } from "@mastra/libsql";
    import { notes } from "./mcp/server";

    export const mastra = new Mastra({
    storage: new LibSQLStore({
    id: 'mastra-storage',
    // stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
    }),
    logger: new PinoLogger({
    name: "Mastra",
    level: "info",
    }),
    mcpServers: {
    notes,
    },
    });
  2. 资源处理器允许客户端发现并读取服务器管理的内容。实现处理器以处理 notes 目录中的 Markdown 文件。将以下内容添加到 src/mastra/mcp/resources.ts 文件中:

    🌐 Resource handlers allow clients to discover and read the content your server manages. Implement handlers to work with markdown files in the notes directory. Add to the src/mastra/mcp/resources.ts file:

    src/mastra/mcp/resources.ts
    import fs from "fs/promises";
    import path from "path";
    import { fileURLToPath } from "url";
    import type { MCPServerResources, Resource } from "@mastra/mcp";

    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    const NOTES_DIR = path.resolve(__dirname, "../../notes"); // relative to the default output directory

    const listNoteFiles = async (): Promise<Resource[]> => {
    try {
    await fs.mkdir(NOTES_DIR, { recursive: true });
    const files = await fs.readdir(NOTES_DIR);
    return files
    .filter((file) => file.endsWith(".md"))
    .map((file) => {
    const title = file.replace(".md", "");
    return {
    uri: `notes://${title}`,
    name: title,
    description: `A note about ${title}`,
    mime_type: "text/markdown",
    };
    });
    } catch (error) {
    console.error("Error listing note resources:", error);
    return [];
    }
    };

    const readNoteFile = async (uri: string): Promise<string | null> => {
    const title = uri.replace("notes://", "");
    const notePath = path.join(NOTES_DIR, `${title}.md`);
    try {
    return await fs.readFile(notePath, "utf-8");
    } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
    console.error(`Error reading resource ${uri}:`, error);
    }
    return null;
    }
    };

    export const resourceHandlers: MCPServerResources = {
    listResources: listNoteFiles,
    getResourceContent: async ({ uri }: { uri: string }) => {
    const content = await readNoteFile(uri);
    if (content === null) return { text: "" };
    return { text: content };
    },
    };

    src/mastra/mcp/server.ts 中注册这些资源处理程序:

    🌐 Register these resource handlers in src/mastra/mcp/server.ts:

    src/mastra/mcp/server.ts
    import { MCPServer } from "@mastra/mcp";
    import { resourceHandlers } from "./resources";

    export const notes = new MCPServer({
    id: "notes",
    name: "Notes Server",
    version: "0.1.0",
    tools: {},
    resources: resourceHandlers,
    });
  3. 工具是你的服务器可以执行的操作。让我们创建一个 write 工具。首先,在 src/mastra/tools/write-note.ts 中定义该工具:

    🌐 Tools are the actions your server can perform. Let's create a write tool. First, define the tool in src/mastra/tools/write-note.ts:

    src/mastra/tools/write-note.ts
    import { createTool } from "@mastra/core/tools";
    import { z } from "zod";
    import { fileURLToPath } from "url";
    import path from "node:path";
    import fs from "fs/promises";

    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    const NOTES_DIR = path.resolve(__dirname, "../../../notes");

    export const writeNoteTool = createTool({
    id: "write",
    description: "Write a new note or overwrite an existing one.",
    inputSchema: z.object({
    title: z
    .string()
    .nonempty()
    .describe("The title of the note. This will be the filename."),
    content: z
    .string()
    .nonempty()
    .describe("The markdown content of the note."),
    }),
    outputSchema: z.string().nonempty(),
    execute: async (inputData) => {
    try {
    const { title, content } = inputData;
    const filePath = path.join(NOTES_DIR, `${title}.md`);
    await fs.mkdir(NOTES_DIR, { recursive: true });
    await fs.writeFile(filePath, content, "utf-8");
    return `Successfully wrote to note \"${title}\".`;
    } catch (error: any) {
    return `Error writing note: ${error.message}`;
    }
    },
    });

    src/mastra/mcp/server.ts 中注册此工具:

    🌐 Register this tool in src/mastra/mcp/server.ts:

    src/mastra/mcp/server.ts
    import { MCPServer } from "@mastra/mcp";
    import { resourceHandlers } from "./resources";
    import { writeNoteTool } from "../tools/write-note";

    export const notes = new MCPServer({
    id: "notes",
    name: "Notes Server",
    version: "0.1.0",
    resources: resourceHandlers,
    tools: {
    write: writeNoteTool,
    },
    });
  4. 提示处理器为客户提供现成的提示。你将添加以下三个:

    🌐 Prompt handlers provide ready-to-use prompts for clients. You'll add these three:

    • 每日注意
    • 总结注意
    • 头脑风暴想法

    这需要安装一些 Markdown 解析库:

    🌐 This requires a few markdown-parsing libraries you need to install:

    npm install unified remark-parse gray-matter @types/unist

    src/mastra/mcp/prompts.ts 中实现这些提示:

    🌐 Implement the prompts in src/mastra/mcp/prompts.ts:

    src/mastra/mcp/prompts.ts
    import type { MCPServerPrompts } from "@mastra/mcp";
    import { unified } from "unified";
    import remarkParse from "remark-parse";
    import matter from "gray-matter";
    import type { Node } from "unist";

    const prompts = [
    {
    name: "new_daily_note",
    description: "Create a new daily note.",
    version: "1.0.0",
    },
    {
    name: "summarize_note",
    description: "Give me a TL;DR of the note.",
    version: "1.0.0",
    },
    {
    name: "brainstorm_ideas",
    description: "Brainstorm new ideas based on a note.",
    version: "1.0.0",
    },
    ];

    function stringifyNode(node: Node): string {
    if ("value" in node && typeof node.value === "string") return node.value;
    if ("children" in node && Array.isArray(node.children))
    return node.children.map(stringifyNode).join("");
    return "";
    }

    export async function analyzeMarkdown(md: string) {
    const { content } = matter(md);
    const tree = unified().use(remarkParse).parse(content);
    const headings: string[] = [];
    const wordCounts: Record<string, number> = {};
    let currentHeading = "untitled";
    wordCounts[currentHeading] = 0;
    tree.children.forEach((node) => {
    if (node.type === "heading" && node.depth === 2) {
    currentHeading = stringifyNode(node);
    headings.push(currentHeading);
    wordCounts[currentHeading] = 0;
    } else {
    const textContent = stringifyNode(node);
    if (textContent.trim()) {
    wordCounts[currentHeading] =
    (wordCounts[currentHeading] || 0) + textContent.split(/\\s+/).length;
    }
    }
    });
    return { headings, wordCounts };
    }

    const getPromptMessages: MCPServerPrompts["getPromptMessages"] = async ({
    name,
    args,
    }) => {
    switch (name) {
    case "new_daily_note":
    const today = new Date().toISOString().split("T")[0];
    return [
    {
    role: "user",
    content: {
    type: "text",
    text: `Create a new note titled \"${today}\" with sections: \"## Tasks\", \"## Meetings\", \"## Notes\".`,
    },
    },
    ];
    case "summarize_note":
    if (!args?.noteContent) throw new Error("No content provided");
    const metaSum = await analyzeMarkdown(args.noteContent as string);
    return [
    {
    role: "user",
    content: {
    type: "text",
    text: `Summarize each section in ≤ 3 bullets.\\n\\n### Outline\\n${metaSum.headings.map((h) => `- ${h} (${metaSum.wordCounts[h] || 0} words)`).join("\\n")}`.trim(),
    },
    },
    ];
    case "brainstorm_ideas":
    if (!args?.noteContent) throw new Error("No content provided");
    const metaBrain = await analyzeMarkdown(args.noteContent as string);
    return [
    {
    role: "user",
    content: {
    type: "text",
    text: `Brainstorm 3 ideas for underdeveloped sections below ${args?.topic ? `on ${args.topic}` : "."}\\n\\nUnderdeveloped sections:\\n${metaBrain.headings.length ? metaBrain.headings.map((h) => `- ${h}`).join("\\n") : "- (none, pick any)"}`,
    },
    },
    ];
    default:
    throw new Error(`Prompt \"${name}\" not found`);
    }
    };

    export const promptHandlers: MCPServerPrompts = {
    listPrompts: async () => prompts,
    getPromptMessages,
    };

    src/mastra/mcp/server.ts 中注册这些提示处理程序:

    🌐 Register these prompt handlers in src/mastra/mcp/server.ts:

    src/mastra/mcp/server.ts
    import { MCPServer } from "@mastra/mcp";
    import { resourceHandlers } from "./resources";
    import { writeNoteTool } from "../tools/write-note";
    import { promptHandlers } from "./prompts";

    export const notes = new MCPServer({
    id: "notes",
    name: "Notes Server",
    version: "0.1.0",
    resources: resourceHandlers,
    prompts: promptHandlers,
    tools: {
    write: writeNoteTool,
    },
    });

运行服务器
Direct link to 运行服务器

🌐 Run the Server

太棒了,你已经创建了你的第一个 MCP 服务器!现在你可以通过启动 Mastra 开发服务器并打开 Studio 来试用它:

🌐 Great, you've authored your first MCP server! Now you can try it out by starting the Mastra dev server and opening Studio:

npm run dev

在浏览器中打开 http://localhost:4111。在左侧边栏中,选择 MCP 服务器。选择 notes MCP 服务器。

🌐 Open http://localhost:4111 in your browser. In the left sidebar, select MCP Servers. Select the notes MCP server.

现在将向你展示如何将 MCP 服务器添加到你的 IDE 的说明。你可以使用任何 MCP 客户端连接此 MCP 服务器。在右侧的 可用工具 下,你还可以选择 写入 工具。

🌐 You'll now be presented with instructions on how to add the MCP server to your IDE. You're able to use this MCP server with any MCP Client. On the right side under Available Tools you can also select the write tool.

write 工具中,尝试输入 test 作为名称,this is a test 作为 Markdown 内容。点击 Submit 后,你将在 notes 中得到一个新的 test.md 文件。

🌐 Inside the write tool, try it out by providing test as a name and this is a test for the markdown content. After clicking on Submit you'll have a new test.md file inside notes.