dailytutorfor.you
& AI Science Data

MCP for Web Developers 2026: A Complete Guide to Building a Model Context Protocol Server with TypeScript

Learn how to build a production-minded MCP server with TypeScript: from architectural concepts, secure tool implementation, and error handling, to best practices so your AI agent can connect to real data and real-world actions.

10 min read

MCP for Web Developers 2026: A Complete Guide to Building a Model Context Protocol Server with TypeScript

Estimated reading time: 15 minutes
Level: Intermediate
Stack: Node.js + TypeScript + Model Context Protocol SDK

1) Introduction — What and Why

In 2026, the question for developers is no longer "how do we build a chatbot?" but "how do we build AI that can actually work?".
Work here means: reading business data, executing safe actions, and remaining auditable.

This is where Model Context Protocol (MCP) becomes important. You can think of MCP as USB-C for AI: one connection standard for many data sources and tools. Instead of building custom integrations for each model/client, you only need to expose capabilities through MCP, then any MCP-supporting client (editor, AI app, CLI, etc.) can use your server.

Why is this relevant for web developers?

  • You are already used to building APIs, auth, and business logic.
  • An MCP server is basically an "API for AI agents" with a standardized format.
  • Reusable: one server can be used across hosts/clients.

Real-world examples:

  • Support team: an AI agent reads the knowledge base + calls ticketing tools.
  • Engineering team: an agent can check CI/CD status, read logs, and help triage issues.
  • Ops team: an agent queries metrics + triggers incident workflows with guardrails.

In short: MCP turns AI from "just chatting" into "able to execute".


2) Prerequisites — What You Need Before Starting

Minimum setup you need:

  1. Node.js 20+ (22+ recommended)
  2. Basic TypeScript (interfaces, async/await, typing)
  3. Understand HTTP API and JSON concepts
  4. Your preferred editor (VS Code/Cursor/etc.)
  5. Optional but very helpful: MCP Inspector for testing

Install core dependencies:

mkdir mcp-tutorial && cd mcp-tutorial npm init -y npm install @modelcontextprotocol/server zod npm install -D typescript tsx @types/node npx tsc --init

Add the script in package.json:

{ "scripts": { "dev": "tsx src/server.ts" } }

3) Core Concepts — Fundamentals (Using Analogies So It Sticks)

Imagine you are building an automated restaurant:

  • Host = dining area (where users interact with AI)
  • MCP Client = waiter (bridge between host and kitchen)
  • MCP Server = kitchen (provides real capabilities)

Inside an MCP server, there are 3 core primitives:

  1. Tools → executable actions (e.g., create ticket, search document)
  2. Resources → contextual data (e.g., config, product list, schema)
  3. Prompts → reusable instruction templates

Common transports:

  • STDIO: local process, fast and simple for local integration.
  • Streamable HTTP: for remote servers, multi-client setups, and HTTP/OAuth auth.

Simple analogy:

  • Tools = action buttons in a dashboard
  • Resources = read-only data panels
  • Prompts = reusable SOP templates

4) Architecture / Diagram

Minimal production-minded MCP server architecture:

+---------------------------+ | MCP Host (AI App / IDE) | +-------------+-------------+ | | JSON-RPC over STDIO / HTTP v +-------------+-------------+ | MCP Server (TypeScript) | |---------------------------| | Tool: search_articles | | Tool: get_article_detail | | Resource: app://health | | Prompt: write_summary | +-------------+-------------+ | | Internal Service Layer v +-------------+-------------+ | Business Logic + Storage | | (DB/API/Files/Vector DB) | +---------------------------+

Execution flow:

  1. Host requests the tool list (tools/list).
  2. The model chooses a tool based on user intent.
  3. Host sends tools/call to the MCP server.
  4. Server validates input (Zod), runs logic, returns structured results.
  5. Host combines results into the AI response.

5) Step-by-Step Implementation (Complete & Runnable Code)

In this section we will build a simple server themed around content knowledge with 2 tools:

  • search_articles(query)
  • get_article_detail(id)

5.1 Create src/server.ts

import { McpServer } from "@modelcontextprotocol/server/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/server/server/stdio.js"; import { z } from "zod"; /** * Dummy data (knowledge base simulation). * In production, replace with DB/API. */ const ARTICLES = [ { id: "art-001", title: "Mengenal MCP untuk Integrasi AI", tags: ["mcp", "ai", "integration"], content: "MCP adalah protokol standar untuk menghubungkan AI ke tools dan data secara konsisten.", url: "https://example.local/articles/art-001", }, { id: "art-002", title: "Best Practices Error Handling di TypeScript", tags: ["typescript", "backend", "best-practice"], content: "Gunakan typed error, validasi input ketat, dan log terstruktur untuk reliability.", url: "https://example.local/articles/art-002", }, { id: "art-003", title: "Arsitektur Agentic App 2026", tags: ["agent", "architecture", "mcp"], content: "Pisahkan planning, tool execution, dan observability agar sistem mudah di-maintain.", url: "https://example.local/articles/art-003", }, ]; /** Utility: async delay simulation */ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); /** Simple query sanitization to prevent weird input */ function normalizeQuery(q: string): string { return q.trim().toLowerCase().replace(/\s+/g, " "); } /** * Create MCP server with metadata + instructions. * Instructions help host/model understand tool usage order. */ const server = new McpServer( { name: "content-knowledge-server", version: "1.0.0", }, { instructions: "Use search_articles first before get_article_detail so IDs are valid and results are more relevant.", capabilities: { logging: {}, }, } ); /** Simple resource for health check */ server.registerResource( "health", "app://health", { title: "Service Health", description: "Health status MCP server", mimeType: "application/json", }, async (uri) => { const payload = { status: "ok", service: "content-knowledge-server", timestamp: new Date().toISOString(), }; return { contents: [ { uri: uri.href, text: JSON.stringify(payload, null, 2), mimeType: "application/json", }, ], }; } ); /** * Tool #1: search articles * - Zod-validated input * - clear error handling * - consistent output */ server.registerTool( "search_articles", { title: "Search Articles", description: "Search articles by text query", inputSchema: z.object({ query: z.string().min(2, "Query minimum 2 characters").max(100), limit: z.number().int().min(1).max(20).default(5), }), }, async ({ query, limit }, ctx) => { try { await ctx.mcpReq.log("info", `search_articles called: query='${query}'`); const normalized = normalizeQuery(query); await sleep(80); // I/O simulation const results = ARTICLES.filter((a) => { const haystack = `${a.title} ${a.tags.join(" ")} ${a.content}`.toLowerCase(); return haystack.includes(normalized); }) .slice(0, limit) .map((a) => ({ id: a.id, title: a.title, url: a.url, snippet: a.content.slice(0, 120), })); const payload = { results, total: results.length }; return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload, }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; await ctx.mcpReq.log("error", `search_articles failed: ${message}`); return { isError: true, content: [ { type: "text", text: JSON.stringify({ error: "Failed to search articles", detail: message }), }, ], }; } } ); /** * Tool #2: get article detail by id */ server.registerTool( "get_article_detail", { title: "Get Article Detail", description: "Get article detail by ID", inputSchema: z.object({ id: z.string().regex(/^art-\d{3}$/, "ID format must be art-xxx"), }), }, async ({ id }, ctx) => { try { await ctx.mcpReq.log("info", `get_article_detail called: id='${id}'`); await sleep(50); const article = ARTICLES.find((a) => a.id === id); if (!article) { return { isError: true, content: [ { type: "text", text: JSON.stringify({ error: "Article not found", id }), }, ], }; } const payload = { id: article.id, title: article.title, tags: article.tags, content: article.content, url: article.url, }; return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload, }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; await ctx.mcpReq.log("error", `get_article_detail failed: ${message}`); return { isError: true, content: [ { type: "text", text: JSON.stringify({ error: "Failed to get detail", detail: message }), }, ], }; } } ); /** Reusable prompt template */ server.registerPrompt( "write_summary", { title: "Write Summary", description: "Template for summarizing technical articles", argsSchema: z.object({ audience: z.string().default("intermediate developers"), style: z.string().default("concise and actionable"), }), }, ({ audience, style }) => ({ messages: [ { role: "user", content: { type: "text", text: `Summarize the article for ${audience} in a ${style} style. Include 3 implementation points.`, }, }, ], }) ); async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); // Important: for STDIO, avoid console.log to stdout. // If manual logging is needed, write to stderr. process.stderr.write("MCP server running via STDIO... "); } catch (error) { const message = error instanceof Error ? error.message : String(error); process.stderr.write(`Fatal error: ${message} `); process.exit(1); } } main();

5.2 Run the server

npm run dev

5.3 Quick test with MCP Inspector

npx @modelcontextprotocol/inspector npm run dev

In the inspector UI, check:

  • tools/list shows search_articles and get_article_detail
  • call search_articles with query mcp
  • take one ID, then call get_article_detail

If these two tools run stably, your server foundation is already solid ✅


6) Best Practices — Tips from Industry Practice

  1. Validate input as strictly as possible
    Never trust raw model input. Use Zod schemas for every tool.

  2. Separate protocol layer from business logic
    MCP handlers should be adapters only. Keep core logic in a service layer for better testability.

  3. Use structured logging
    Log levels (info, warn, error) + request context improve observability.

  4. Fail safely
    On error, return isError: true with safe messages. Never leak secrets or sensitive stack traces.

  5. Apply the principle of least privilege
    Destructive tools (delete/update) should have extra guardrails (approval, role checks, audit logs).

  6. Rate limiting & timeout
    For HTTP servers, limit request size, timeout, and concurrency to resist abuse.

  7. Security hardening for remote transport
    If exposed over HTTP, plan for auth token/OAuth, CORS allowlist, and sensitive-log redaction.


7) Common Mistakes — Frequently Seen Pitfalls

  1. Writing logs to stdout when using STDIO
    This can break JSON-RPC framing. Use stderr or context logging APIs.

  2. Overloaded tools
    One tool doing too many things. Split into smaller tools for predictability.

  3. Loose schemas
    Inputs without length/format limits are vulnerable to prompt abuse and inconsistent outputs.

  4. Error messages too generic or too detailed
    Too generic is hard to debug, too detailed can leak sensitive information.

  5. No cross-tool instructions
    Without instructions, the model can call tools in the wrong order.

  6. Going to production without inspection
    At minimum, test in inspector + run integration tests before critical workflow rollout.


8) Advanced Tips — If You Want to Level Up

A. Add metadata for result ranking

Store score, source, updatedAt in search output so host/model can choose context more accurately.

B. Implement caching

For popular queries, a cache layer (e.g., Redis/in-memory TTL) can significantly reduce latency.

C. Use HTTP transport for team scale

If you need multi-user and remote access, consider streamable HTTP + session management.

D. Create contract tests per tool

Each tool should have tests for:

  • valid input
  • invalid input
  • downstream error
  • timeout

E. Add an audit trail

For side-effect tools, store traces: who (host/user), when, what action, success/failure status.

F. Version your capabilities

Use a consistent naming/version policy so schema changes do not break older clients.


9) Summary & Next Steps

We covered everything from zero to a runnable TypeScript MCP server:

  • understanding why MCP matters in the era of agentic software,
  • understanding host-client-server architecture,
  • implementing tools/resources/prompts with validation and error handling,
  • and learning security and reliability best practices.

Recommended next steps:

  1. Replace dummy data with real DB/API.
  2. Add auth + rate limiting if using remote HTTP.
  3. Integrate observability (log aggregation + metrics).
  4. Add automated tests in CI.
  5. Release iteratively: start with read-only tools, then side-effect tools.

If you are building AI-based web products in 2026, MCP is no longer a "nice to have" — it is the interoperability foundation that keeps your system scalable and not locked to one vendor.


10) References


Trend research notes (brief)

This topic was selected because it consistently appears in:

  • GitHub Trending: many repositories related to AI agents and tool-augmented workflows.
  • Medium/dev communities: growing articles on agentic engineering and AI integration.
  • 2026 search results: spike in "MCP tutorial/guide" content and official MCP 2026 roadmap.