Créer un serveur MCP de notes
Dans ce guide, vous apprendrez à créer un serveur MCP (Model Context Protocol) complet à partir de zéro. Ce serveur gérera une collection de notes Markdown et offrira les fonctionnalités suivantes :
- Lister et lire des notes : permettre aux clients de parcourir et d’afficher les fichiers Markdown stockés sur le serveur
- Écrire des notes : fournir un Tool pour créer ou mettre à jour des notes
- Proposer des prompts intelligents : générer des prompts contextuels, par exemple pour créer un modèle de note quotidienne ou résumer un contenu existant
PrérequisLien direct vers Prérequis
- Node.js
v22.13.0ou une version ultérieure installé - Une clé API d’un fournisseur de modèles pris en charge
- Un projet Mastra existant (suivez le guide d’installation pour configurer un nouveau projet)
Ajouter les dépendances et fichiers nécessairesLien direct vers Ajouter les dépendances et fichiers nécessaires
Avant de créer un serveur MCP, vous devez installer des dépendances supplémentaires et mettre en place une structure de dossiers standard.
Ajoutez
@mastra/mcpà votre projet :- npm
- pnpm
- Yarn
- Bun
npm install @mastra/mcp@latestpnpm add @mastra/mcp@latestyarn add @mastra/mcp@latestbun add @mastra/mcp@latestAprès avoir suivi le guide d’installation par défaut, votre projet contiendra des fichiers qui ne sont pas pertinents pour ce guide. Vous pouvez les supprimer sans risque :
rm -rf src/mastra/agents src/mastra/workflows src/mastra/tools/weather-tool.tsVous devez également modifier le fichier
src/mastra/index.tscomme suit :src/mastra/index.tsimport { 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.dburl: ':memory:',}),logger: new PinoLogger({name: 'Mastra',level: 'info',}),})Créez un répertoire dédié à la logique de votre serveur MCP, ainsi qu’un répertoire
notespour vos notes :mkdir notes src/mastra/mcpCréez les fichiers suivants :
touch src/mastra/mcp/{server,resources,prompts}.tsserver.ts: contiendra la configuration principale du serveur MCPresources.ts: gérera le listage et la lecture des fichiers de notesprompts.ts: contiendra la logique des prompts intelligents
La structure de répertoires obtenue doit ressembler à ceci :
<your-project-name>/├── notes/└── src/└── mastra/├── index.ts├── mcp/│ ├── server.ts│ ├── resources.ts│ └── prompts.ts└── tools/
Créer le serveur MCPLien direct vers Créer le serveur MCP
Ajoutons le serveur MCP !
Dans
src/mastra/mcp/server.ts, définissez l’instance du serveur MCP :src/mastra/mcp/server.tsimport { MCPServer } from '@mastra/mcp'export const notes = new MCPServer({id: 'notes',name: 'Notes Server',version: '0.1.0',tools: {},})Enregistrez ce serveur MCP dans votre instance Mastra, dans
src/mastra/index.ts. La clénotesest l’identifiant public de votre serveur MCP :src/mastra/index.tsimport { 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.dburl: ':memory:',}),logger: new PinoLogger({name: 'Mastra',level: 'info',}),mcpServers: {notes,},})Les gestionnaires de ressources permettent aux clients de découvrir et de lire le contenu géré par votre serveur. Implémentez des gestionnaires pour travailler avec les fichiers Markdown du répertoire
notes. Ajoutez ceci au fichiersrc/mastra/mcp/resources.ts:src/mastra/mcp/resources.tsimport 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 directoryconst 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 }},}Enregistrez ces gestionnaires de ressources dans
src/mastra/mcp/server.ts:src/mastra/mcp/server.tsimport { 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,})Les Tools sont les actions que votre serveur peut effectuer. Créons un Tool
write. Définissez d’abord le Tool danssrc/mastra/tools/write-note.ts:src/mastra/tools/write-note.tsimport { 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 } = inputDataconst 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}`}},})Enregistrez ce Tool dans
src/mastra/mcp/server.ts:src/mastra/mcp/server.tsimport { 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,},})Les gestionnaires de prompts fournissent aux clients des prompts prêts à l’emploi. Vous ajouterez les trois suivants :
- Note quotidienne
- Résumer une note
- Réfléchir à des idées
Cela nécessite quelques bibliothèques d’analyse Markdown que vous devez installer :
npm install unified remark-parse gray-matter @types/unistImplémentez les prompts dans
src/mastra/mcp/prompts.ts:src/mastra/mcp/prompts.tsimport 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.valueif ('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] = 0tree.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,}Enregistrez ces gestionnaires de prompts dans
src/mastra/mcp/server.ts:src/mastra/mcp/server.tsimport { 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,},})
Exécuter le serveurLien direct vers Exécuter le serveur
Excellent, vous avez créé votre premier serveur MCP ! Vous pouvez maintenant l’essayer en démarrant le serveur de développement Mastra et en ouvrant Studio :
npm run dev
Ouvrez http://localhost:4111 dans votre navigateur. Dans la barre latérale gauche, sélectionnez MCP Servers, puis le serveur MCP notes.
Des instructions vont maintenant s’afficher pour ajouter le serveur MCP à votre IDE. Vous pouvez utiliser ce serveur MCP avec n’importe quel client MCP. À droite, sous Available Tools, vous pouvez aussi sélectionner le Tool write.
Dans le Tool write, essayez en indiquant test comme nom et this is a test comme contenu Markdown. Après avoir sélectionné Submit, un nouveau fichier test.md se trouvera dans notes.