Person typing on laptop with focus on keyboard representing software development and programming tutorials
tutorials

MCP in Practice: Build Your First TypeScript Server in 30 Minutes (2026)

NeuralPulse|19 de maio de 2026|12 min read|Ler em Português

If you work with development and AI, you've heard of MCP. But if you haven't gotten your hands on the code yet, it's time.

In March 2026, the Model Context Protocol SDK reached 97 million monthly downloads — a growth of 4,750% since its launch in November 2024 (Digital Applied). The public registry of MCP servers jumped from ~1,200 in Q1 2025 to over 9,400 in April 2026 (Digital Applied). OpenAI, Google DeepMind, Microsoft, Cohere, and Mistral — all have adopted the protocol (WorkOS Blog, March/2026).

"MCP has done in 16 months what took REST APIs several years: become the default infrastructure layer for a new category of computing."Digital Applied, MCP Hits 97M Downloads: Model Context Protocol Guide (March/2026)

This tutorial is practical. We will build a functional MCP server in TypeScript, connect it to three different clients, and understand why this protocol became the standard for AI integration in 2026.

1. What We Will Build

We will create a Task Manager MCP Server — a server that exposes tools for managing tasks: add, list, and complete. It seems simple, but it's enough for you to understand the entire flow:

  • MCP Server with TypeScript + official SDK
  • Three tools with schema validation
  • Transport via stdio (for desktop) and mention of Streamable HTTP
  • Connection to Claude Desktop, Cursor, and Claude Code

In the end, you will have a functional server that you can extend for any real-world case — database queries, external API calls, file manipulation.

2. Project Setup

Create a folder and initialize the project:

mkdir mcp-task-manager
cd mcp-task-manager
npm init -y

Install the official SDK:

npm install @modelcontextprotocol/sdk zod

The zod package is optional but recommended for schema validation. The MCP SDK uses it internally.

Your package.json needs the type: "module" field to use ESM:

{
  "name": "mcp-task-manager",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Create a basic tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "declaration": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

Done. The final structure will be:

mcp-task-manager/
├── package.json
├── tsconfig.json
└── src/
    └── index.ts

3. Creating the Server — The Code

Now for the good part. Create src/index.ts with the complete MCP server.

The MCP SDK exports classes and schemas that follow the protocol standard. Here's the structure:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

Types and State

interface Task {
  id: string;
  title: string;
  completed: boolean;
  createdAt: string;
}

// Our in-memory "database" — replace with a real DB later const tasks: Map<string, Task> = new Map(); let nextId = 1;

Server Instance

const server = new Server(
  {
    name: "task-manager",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

The second parameter declares the server's capabilities. Here we say it exposes tools. MCP also supports resources, prompts, and logging.

Listing the Tools

The ListToolsRequestSchema handler informs the client which tools are available and their input schemas:

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "add_task",
      description: "Adds a new task to the list",
      inputSchema: {
        type: "object",
        properties: {
          title: {
            type: "string",
            description: "Task title",
          },
        },
        required: ["title"],
      },
    },
    {
      name: "list_tasks",
      description: "Lists registered tasks",
      inputSchema: {
        type: "object",
        properties: {
          filter: {
            type: "string",
            enum: ["all", "pending", "done"],
            description: "Filter by status",
          },
        },
      },
    },
    {
      name: "complete_task",
      description: "Marks a task as completed",
      inputSchema: {
        type: "object",
        properties: {
          id: {
            type: "string",
            description: "Task ID",
          },
        },
        required: ["id"],
      },
    },
  ],
}));

Each tool follows the JSON Schema standard for validation — the client itself validates the parameters before calling.

Executing the Tools

The CallToolRequestSchema handler receives the tool name and arguments:

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

switch (name) { case "add_task": { const id = String(nextId++); const task: Task = { id, title: args?.title as string, completed: false, createdAt: new Date().toISOString(), }; tasks.set(id, task); return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }], }; }

case "list_tasks": {
  const filter = (args?.filter as string) || "all";
  let result = Array.from(tasks.values());
  if (filter === "pending") result = result.filter((t) => !t.completed);
  if (filter === "done") result = result.filter((t) => t.completed);
  return {
    content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
  };
}
case "complete_task": {
  const id = args?.id as string;
  const task = tasks.get(id);
  if (!task) {
    throw new Error(`Task ${id} not found`);
  }
  task.completed = true;
  return {
    content: [{ type: "text", text: JSON.stringify(task, null, 2) }],
  };
}
default:
  throw new Error(`Unknown tool: ${name}`);

} });

Notice the response pattern: { content: [{ type: "text", text: "..." }] }. MCP accepts multiple content types — text, image, resource, and embedded.

Initializing the Server

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("✅ Task Manager MCP Server running via stdio");
}

main().catch((error) => { console.error("Fatal:", error); process.exit(1); });

⚠️ Use console.error for logs. console.log is captured by the stdio transport and can break communication with the client.

Compile and test:

npx tsc
node dist/index.js

If everything went well, you'll see the green message in the terminal. The server is running and waiting for connections.

4. Transports: stdio vs Streamable HTTP

MCP supports two main transports. Each has its use case:

FeaturestdioStreamable HTTP
ConnectionChild process via stdin/stdoutHTTP requests
LatencyVery low (same process)Moderate (network)
Ideal forClaude Desktop, Claude CodeRemote web servers, Cursor
SharingLocal onlyRemote (via URL)
AuthenticationNot neededJWT / API Key
ConfigurationPoint command + argsURL + headers
StateEphemeral (dies with client)Persistent

In the example above we used stdio, the simplest transport. To use Streamable HTTP, install the additional package and swap the transport:

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

// Instead of StdioServerTransport: const transport = new StreamableHTTPServerTransport({ port: 3100, endpoint: "/mcp", });

Streamable HTTP allows any HTTP client to consume your server — including from other machines. Perfect for teams and enterprise integrations.

Companies using MCP in production convert pilots at a rate of 38%, compared to only 22% without the protocol — a difference of 16 percentage points (Digital Applied, The MCP Adoption Wave, April/2026).

5. Connecting to Claude Desktop

Claude Desktop was the first MCP client and remains the most used. To connect your server:

  1. Open Claude Desktop settings
  2. Go to Settings > Developer > Edit Config
  3. Add to claude_desktop_config.json:
{
  "mcpServers": {
    "task-manager": {
      "command": "node",
      "args": ["C:/absolute/path/mcp-task-manager/dist/index.js"]
    }
  }
}

On macOS / Linux:

{
  "mcpServers": {
    "task-manager": {
      "command": "node",
      "args": ["/absolute/path/mcp-task-manager/dist/index.js"]
    }
  }
}
  1. Restart Claude Desktop
  2. Look in the bottom right corner — the tool icon 🔧 appears indicating the server is active

Now you can ask: "Add a task to review the monthly report" and Claude will call your add_task tool automatically.

6. Connecting to Cursor and Claude Code

Cursor

In Cursor, go to Settings > Features > MCP and add:

FieldValue
Nametask-manager
Typecommand
Commandnode C:/path/mcp-task-manager/dist/index.js

After adding it, Cursor automatically detects your server's tools and makes them available to the AI agent during development.

Tip: In Cursor, use Cmd+I to open the Composer and ask something like "Use the list_tasks tool to see what's pending". The AI will call your MCP server in real-time.

Claude Code

With Claude Code (CLI), the configuration is per project. Create or edit the file ~/.claude/claude.json:

{
  "mcpServers": {
    "task-manager": {
      "command": "node",
      "args": ["/path/mcp-task-manager/dist/index.js"]
    }
  }
}

Then, inside your project, just run claude and start using the tools.

Over 300 clients already support the MCP protocol — including VS Code / GitHub Copilot, Windsurf, and dozens of IDEs and no-code tools (Digital Applied).

7. Verification Checklist

Before you go off building the next great MCP server, check that everything is working:

  • The server compiles without errors with npx tsc
  • The server starts and shows the log message in the terminal
  • Claude Desktop recognizes the server (🔧 icon appears)
  • Cursor lists the server's tools in the MCP configuration
  • add_task returns a task with a unique ID
  • list_tasks returns the correct list with filters
  • complete_task marks the task as completed and returns an error for non-existent IDs
  • Responses are always in the format { content: [{ type: "text", text: ... }] }

If any item fails, the most common problem is the absolute path in the configuration JSON — use the full path to dist/index.js.

8. What's Next?

Your first MCP server is live. The official modelcontextprotocol/servers repository has already surpassed 85,600 stars on GitHub, with the TypeScript SDK at 12,300+ stars and 94 releases (GitHub — modelcontextprotocol/servers, GitHub — typescript-sdk). The community is growing fast.

To evolve your server:

  • Add resources to expose data the client can read (e.g., task statistics)
  • Implement prompts for reusable templates
  • Swap the in-memory database for SQLite or PostgreSQL
  • Publish it on the MCP registry — the projection for Q3 2026 is 18,400 published servers and 38-46% of the Fortune 1000 with at least one MCP integration in production (Digital Applied, MCP Adoption Q3 2026 Projection)

"When Anthropic quietly shipped the Model Context Protocol in November 2024, it looked like any other internal tool that got open-sourced on a slow news day. Eighteen months later, MCP has 97 million monthly SDK downloads, over 9,400 servers, 300-plus clients, and a Linux Foundation home co-founded by Anthropic, OpenAI, and Block."Digital Applied, MCP Adoption Statistics 2026 (April/2026)

MCP is today what REST was for the 2000s — the standard integration layer for a new category of computing. Having your own server running is not just a technical exercise. It's preparation for what's coming.

Access modelcontextprotocol.io/docs for the official documentation, and the repository modelcontextprotocol/typescript-sdk for the source code.

Let's code. 🚀

Related Articles

Also check out: How to Use AI to Create High-Quality Content in 2026 Also check out: Practical Guide: How to Create Your First AI Assistant with $0 in 30 Minutes Also check out: Free AI APIs in 2026: Google Dropped 80%, OpenAI Requires a Card — The Map of What Still Works

#mcp#typescript#model-context-protocol#tutorial#claude#sdk
Compartilhar: