Skip to main content

Registering Tools — Build Your First Server

← Back to Dashboard
1. Project Scaffolding2. Registering Tools3. Adding Resources & Prompts4. Connecting & Publishing

Registering Tools

📚 Build Your First Server⏱ 12 min⭐ 90 XP⌨ Hands-on lab

Your Server's First Superpower

Tools are the most commonly used MCP capability. Let's build a practical tool that searches a local notes directory.

Complete Tool Implementation

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { readdir, readFile, writeFile } from "fs/promises";
import { join } from "path";

const server = new McpServer({
  name: "notes-server",
  version: "1.0.0"
});

// Tool 1: Search notes by keyword (using registerTool — replaces deprecated server.tool)
server.registerTool(
  "search_notes",
  {
    title: "Search Notes",
    description: "Search all markdown notes for a keyword. Returns matching filenames and snippets.",
    inputSchema: z.object({
      query: z.string().describe("The keyword to search for"),
      maxResults: z.number().optional().default(5).describe("Max results to return")
    }),
    annotations: { readOnlyHint: true }
  },
  async ({ query, maxResults }) => {
    const notesDir = process.env.NOTES_DIR || "./notes";
    const files = await readdir(notesDir);
    const matches: string[] = [];

    for (const file of files) {
      if (!file.endsWith(".md")) continue;
      const content = await readFile(join(notesDir, file), "utf-8");
      if (content.toLowerCase().includes(query.toLowerCase())) {
        const lines = content.split("\n");
        const matchLine = lines.find(l =>
          l.toLowerCase().includes(query.toLowerCase())
        );
        matches.push(`**${file}**: ${matchLine?.trim() || "(match in body)"}`);
      }
      if (matches.length >= maxResults) break;
    }

    if (matches.length === 0) {
      return {
        content: [{ type: "text", text: `No notes found matching "${query}".` }]
      };
    }

    return {
      content: [{ type: "text", text: matches.join("\n") }]
    };
  }
);

// Tool 2: Create a new note
server.registerTool(
  "create_note",
  {
    title: "Create Note",
    description: "Create a new markdown note file with the given title and content.",
    inputSchema: z.object({
      title: z.string().describe("Note title (used as filename)"),
      body: z.string().describe("Markdown content of the note")
    })
  },
  async ({ title, body }) => {
    const notesDir = process.env.NOTES_DIR || "./notes";
    const filename = title.toLowerCase().replace(/\s+/g, "-") + ".md";
    const fullPath = join(notesDir, filename);

    try {
      await writeFile(fullPath, `# ${title}\n\n${body}\n`);
      return {
        content: [{ type: "text", text: `✅ Note created: ${filename}` }]
      };
    } catch (e: any) {
      return {
        isError: true,
        content: [{ type: "text", text: `Failed to create note: ${e.message}` }]
      };
    }
  }
);

Tool Registration Patterns

PatternWhen to UseExample
Simple ToolSingle action, no side effectssearch_notes - reads data
Mutating ToolCreates, updates, or deletes datacreate_note - writes files
Async ToolCalls external APIs with latencyfetch_weather - HTTP request
Streaming ToolReturns progress updatesrun_migration - long process
💡 Key Insight: Always include .describe() on every Zod field. The LLM reads these descriptions to decide what values to pass. A missing description means the LLM guesses - and it will guess wrong.
⌨ HANDS-ON LABVerify Server TypeScript SDK Imports
⭐ +125 XP

Inspect the tool registration syntax using the official TypeScript MCP SDK and Zod.

1Verify @modelcontextprotocol/sdk server module can be imported.
lab-sandbox — simulated environment
INFINITY LAB SANDBOX v2.6 — simulated shell
Type the command for the current objective. Helpers: "hint", "solution", "clear".
$
OBJECTIVE 1 / 1 — type "hint" if stuck
🧪 Knowledge Check
Press 1-4 to select1 of 3
What must every tool handler function return?
A raw string
A Promise<void>
An object with a 'content' array containing text/image blocks
A JSON object