TM

tarekmonowar/mcp

Developer tools
0 stars 0 forks Quality 48 Trend 0

Learning MCP tools, resources, and prompts with server–client architecture using STDIO and HTTP (SSE) transports.

Overview

A hands-on learning repository that teaches the from scratch. Each file demonstrates a core MCP concept — , , , , and — with real, runnable code. is an open standard created by Anthropic that lets AI models (like Claude, GPT, etc.) connect to through a unified protocol. Think of it like a — just as USB lets any device connect to any computer, MCP lets any AI connect to any tool or data source. Tools are the AI can call. Think of them as API endpoints the AI can invoke. Resources are the AI can access. Like reading a file or fetching a config. Prompts are with dynamic arguments. They guide the AI's behavior. This file shows the — one tool, one transport, ready to go. This file is the version of the tool server with detailed comments and examples.

README

🔌 Learning MCP (Model Context Protocol)

Tools, Resources, and Prompts with Server–Client Architecture using STDIO & HTTP (SSE) Transports

A hands-on learning repository that teaches the Model Context Protocol (MCP) from scratch. Each file demonstrates a core MCP concept — Tools, Resources, Prompts, Transports, and Client Integration — with real, runnable code.


📖 Table of Contents


🧠 What is MCP?

MCP (Model Context Protocol) is an open standard created by Anthropic that lets AI models (like Claude, GPT, etc.) connect to external tools, data sources, and prompt templates through a unified protocol.

Think of it like a USB port for AI — just as USB lets any device connect to any computer, MCP lets any AI connect to any tool or data source.

Without MCP

User → AI Model → ❌ Can't access external tools, APIs, databases

With MCP

User → AI Model → MCP Client → MCP Server → ✅ Tools, Data, APIs, Databases

🏗 How MCP Works — The Big Picture

┌──────────────────────────────────────────────────────────────────┐
│                        USER / AI HOST                            │
│  (Claude Desktop, VS Code Copilot, Custom Chatbot, etc.)         │
│                                                                  │
│   ┌────────────┐     ┌────────────┐     ┌────────────┐           │
│   │ MCP Client │     │ MCP Client │     │ MCP Client │           │
│   └─────┬──────┘     └─────┬──────┘     └─────┬──────┘           │
│         │                  │                  │                   │
└─────────┼──────────────────┼──────────────────┼──────────────────┘
          │ STDIO            │ STDIO            │ HTTP/SSE
          ▼                  ▼                  ▼
   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
   │  MCP Server  │  │  MCP Server  │  │  MCP Server  │
   │  (Tools)     │  │  (Resources) │  │  (Prompts)   │
   └──────────────┘  └──────────────┘  └──────────────┘

Key Roles:

Component Role
Host The application the user interacts with (e.g., Claude Desktop)
MCP Client Lives inside the Host; manages the connection to servers
MCP Server Exposes tools, resources, or prompts to the AI
Transport The communication channel — STDIO or HTTP (SSE)

🧩 Core Concepts

MCP servers can expose three types of capabilities to AI models:

1. 🔧 Tools — “Actions the AI Can Perform”

Tools are functions the AI can call. Think of them as API endpoints the AI can invoke.

Example: "Track a package", "Send an email", "Query a database"

2. 📦 Resources — “Data the AI Can Read”

Resources are static or dynamic data the AI can access. Like reading a file or fetching a config.

Example: "List of airports", "Company FAQ", "Refund policy document"

3. 💬 Prompts — “Pre-built Prompt Templates”

Prompts are reusable message templates with dynamic arguments. They guide the AI’s behavior.

Example: "Act as a support agent for issue: {issue}"

📁 Project Structure

mcp/
├── server.ts            # ✅ Minimal MCP server with one tool (entry point)
├── toolServer.ts        # 🔧 Detailed tool registration with comments
├── resourceServer.ts    # 📦 Resource registration (static data exposure)
├── promtServer.ts       # 💬 Prompt template registration
├── clientsInfo.ts       # 🖥️  Client-side connection & LLM integration flow
├── info.txt             # 📝 Notes on STDIO vs SSE transport behavior
├── package.json         # 📋 Dependencies & scripts
├── .gitignore           # 🚫 Ignores node_modules
└── README.md            # 📖 You are here!

🚀 Getting Started

Prerequisites

  • Node.js v18+ installed
  • npm (comes with Node.js)

Installation

# 1. Clone the repository
git clone 
cd mcp

# 2. Install dependencies
npm install

# 3. Run the default server (server.ts)
npm start

Dependencies

Package Purpose
@modelcontextprotocol/sdk Official MCP SDK for building servers/clients
zod Schema validation for tool/prompt inputs
tsx Run TypeScript files directly (dev)
typescript TypeScript compiler

📝 File-by-File Code Walkthrough


1. server.ts — Minimal MCP Server

📍 This is the entry point (npm start runs this file)

This file shows the simplest possible MCP server — one tool, one transport, ready to go.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

What’s imported:

Import Why
McpServer The main class to create an MCP server
StdioServerTransport Communicates via standard input/output (CLI)
z (Zod) Validates the input the AI sends to the tool

Creating the server:

const server = new McpServer({
  name: "tracking-orders",    // Unique name for this server
  version: "1.0.0",           // Version — update when you add/change tools
});

Registering a tool:

server.registerTool(
  "trackPackage",                              // Tool name (AI uses this to call it)
  {
    title: "Track Package",                    // Human-readable title
    description: "Track package delivery status", // AI reads this to decide when to use it
    inputSchema: {
      trackingNumber: z.string().describe("Package tracking number"),
      // ↑ Zod schema: the AI must send a string called "trackingNumber"
    },
  },
  async ({ trackingNumber }) => {              // Handler function
    return {
      content: [
        {
          type: "text",
          text: `Checking delivery status for: ${trackingNumber}, and its expected delivery date is 2026-12-31.`,
        },
      ],
    };
  },
);

How the return format works:

// Tools ALWAYS return this structure:
return {
  content: [
    { type: "text", text: "Your response here" }    // Text response
    // { type: "image", data: "base64..." }          // Image response
    // { type: "resource", resource: {...} }          // Resource response
  ]
};

Starting the server:

async function main() {
  const transport = new StdioServerTransport();  // Use STDIO transport
  await server.connect(transport);               // Connect server to transport
  console.error("🌤️  Tracking Orders MCP Server Started!");
  // ⚠️ console.error() NOT console.log() — explained below!
}
main().catch(console.error);

2. toolServer.ts — Deep Dive into Tools

This file is the annotated, learning-focused version of the tool server with detailed comments and examples.

Key difference — Zod validation:

inputSchema: {
  trackingNumber: z.string().length(2).describe("Package tracking number"),
  //                        ^^^^^^^^
  //                        Must be exactly 2 characters long
},

Zod lets you add validation rules so the AI sends the correct input format.

Common Zod validators:

z.string()                    // Must be a string
z.string().min(5)             // At least 5 characters
z.string().length(10)         // Exactly 10 characters
z.number()                    // Must be a number
z.number().min(1).max(100)    // Number between 1-100
z.boolean()                   // true or false
z.enum(["a", "b", "c"])       // One of these values
z.string().optional()         // Optional field

Multiple return types a tool can send:

// ✅ Text response
{ type: "text", text: "Order found" }

// ✅ Image response
{ type: "image", data: "base64..." }

// ✅ Resource reference
{ type: "resource", resource: {...} }

// ✅ Structured content (extra metadata)
{
  content: [{ type: "text", text: "Package shipped" }],
  structuredContent: {
    status: "shipped",
    eta: "tomorrow"
  }
}

3. resourceServer.ts — Exposing Static Data

Resources let you expose read-only data that the AI can access — like documentation, configs, or lists.

server.registerResource(
  "flights airports",          // Resource name
  "flights://airports",        // URI — unique address for this resource
  {
    title: "Supported Airports",
    description: "List of supported airport codes",
    mimeType: "text/plain",    // Format of the data
  },
  async () => {
    // ⚠️ Resources take NO input parameters — they just return data
    return {
      contents: [
        {
          uri: "flights://airports",
          text: `Supported Airports:
- JFK (New York)
- LHR (London Heathrow)
- SFO (San Francisco)`,
          mimeType: "text/plain",
        },
      ],
    };
  },
);

Key differences between Tools and Resources:

Feature Tool 🔧 Resource 📦
Purpose Perform an action Read/expose data
Input Has input parameters No input parameters
Triggered by AI calls it when needed AI reads it for context
Example “Track package #AB123” “Show me airport list”
Return key content: [...] contents: [...]

Understanding the URI system:

"flights://airports"     →  flights = scheme, airports = path
"docs://refund-policy"   →  docs = scheme, refund-policy = path
"faq://shipping"         →  faq = scheme, shipping = path

Common MIME types:

MIME Type Use Case
text/plain Simple text
text/html HTML content
text/markdown Markdown documents
application/json JSON data
image/png PNG images

MIME = Multipurpose Internet Mail Extensions

You can register multiple tools AND resources on the same server:

server.registerTool(...)      // Tool 1
server.registerTool(...)      // Tool 2
server.registerResource(...)  // Resource 1
server.registerResource(...)  // Resource 2

4. promtServer.ts — Prompt Templates

Prompts are reusable message templates that guide the AI’s behavior with dynamic arguments.

server.registerPrompt(
  "support-agent",              // Prompt name
  {
    title: "Support Assistant",
    description: "Handles customer queries",
    argsSchema: {
      issue: z.string(),        // Dynamic argument: the customer's issue
    },
  },
  async ({ issue }) => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `You are a helpful ecommerce support agent. Please help with this issue: ${issue}`,
          },
        },
      ],
    };
  },
);

How it works:

1. Client requests prompt "support-agent" with { issue: "Where is my order?" }
2. Server returns a pre-built message:
   → "You are a helpful ecommerce support agent. Please help with this issue: Where is my order?"
3. This message is sent to the LLM as context

Comparison of all three capabilities:

Feature Tool 🔧 Resource 📦 Prompt 💬
Register registerTool registerResource registerPrompt
Input key inputSchema (none) argsSchema
Return key content contents messages
Purpose Execute actions Expose data Template messages

5. clientsInfo.ts — Client-Side Integration

This file shows how an MCP Client connects to a server and integrates with an LLM.

Step 1 — Create the client:

import { Client } from "@modelcontextprotocol/sdk/client";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio";

const client = new Client({
  name: "my-app",
  version: "1.0.0",
});

Step 2 — Choose a transport:

// Option A: STDIO (local, CLI-based)
const transport = new StdioClientTransport({
  command: "node",                // What program to run
  args: ["orders-server.js"],     // What file to execute
});

// Option B: SSE (web-based, remote)
// const transport = new SSEClientTransport({
//   url: "http://localhost:3001/api/mcp"
// });

Step 3 — Connect & discover capabilities:

await client.connect(transport);

const tools = await client.listTools();          // Get all tools
const resources = await client.listResources();  // Get all resources
const prompts = await client.listPrompts();      // Get all prompts

Step 4 — Full LLM integration flow:

┌─────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│  User    │────▶│ MCP Client │────▶│ LLM (AI)   │────▶│ MCP Server │
│ Message  │     │            │◀────│            │◀────│  (Tools)   │
└─────────┘     └────────────┘     └────────────┘     └────────────┘

1. User says: "Track AB123"
2. Client loads available MCP tools
3. Client sends user message + tool list to LLM
4. LLM decides: "I need to call trackPackage tool"
5. Client calls the tool on MCP Server
6. Server returns result to Client
7. Client sends result back to LLM
8. LLM generates final response to User

🔄 Transport Types Explained

MCP supports two transport mechanisms for server–client communication:

STDIO Transport (Standard Input/Output)

Client Process ←── stdin/stdout ──→ Server Process
  • How: Server runs as a child process; communication happens through stdin/stdout pipes
  • When: Local development, CLI tools, desktop apps (like Claude Desktop)
  • Pros: Simple, no network setup, fast
  • Cons: Only works locally (same machine)
// Server side
const transport = new StdioServerTransport();

// Client side
const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"],
});

HTTP/SSE Transport (Server-Sent Events)

Client ←── HTTP requests + SSE stream ──→ Server (remote)
  • How: Server runs as a web server; client connects via HTTP
  • When: Remote servers, web applications, cloud deployments
  • Pros: Works over network, supports multiple clients
  • Cons: More setup, needs a running HTTP server
// Server side
const transport = new SSEServerTransport("/api/mcp", response);

// Client side
const transport = new SSEClientTransport({
  url: "http://localhost:3001/api/mcp",
});

SSE = Server-Sent Events — a web standard for one-way streaming from server to client. In MCP, the Content-Type is text/event-stream (not application/json).


⚠️ Important: console.log vs console.error

This is a critical gotcha when using STDIO transport:

┌───────────────────────────────────────────────────┐
│              STDIO Transport                       │
│                                                   │
│  console.log()  → STDOUT → ❌ BREAKS MCP!         │
│                    (reserved for MCP messages)     │
│                                                   │
│  console.error() → STDERR → ✅ Safe for logging   │
│                    (separate channel)              │
└───────────────────────────────────────────────────┘

Why? In STDIO mode, stdout is the communication channel between client and server. If you use console.log(), your debug messages mix with MCP protocol messages and break everything.

// ❌ WRONG — will break STDIO transport
console.log("Server started");

// ✅ CORRECT — safe for logging
console.error("Server started");

Exception: In HTTP/SSE mode, console.log() is safe because HTTP responses use a different channel than terminal output.

Transport console.log() console.error()
STDIO ❌ Breaks MCP ✅ Safe
HTTP/SSE ✅ Safe ✅ Safe

🔀 Old API vs New API

The MCP SDK has been updated. This repo uses the new API:

Tools

// ❌ Old API
server.tool(name, description, schema, handler)

// ✅ New API
server.registerTool(name, { title, description, inputSchema }, handler)

Resources

// ❌ Old API
server.resource(uri, description, mimeType, dataFunction)

// ✅ New API
server.registerResource(name, uri, { title, description, mimeType }, dataFunction)

Prompts

// ❌ Old API
server.prompt(name, handler)

// ✅ New API
server.registerPrompt(name, { title, description, argsSchema }, handler)

🏛 Architecture Patterns

For Production Projects

/backend
  ├── llm-agent/        # Chatbot logic (LangChain, OpenAI, etc.)
  ├── mcp-client/       # Connects to MCP servers
  └── api-routes/       # REST/GraphQL endpoints

/mcp-server
  ├── tools/            # Tool definitions
  ├── resources/        # Resource definitions
  └── server-setup/     # Transport & server config

For Learning / Small Projects

Backend (single project)
  ├── LLM Integration
  ├── MCP Server (tools + resources + prompts)
  └── MCP Client (stdio transport)

🛠 Tech Stack

Technology Purpose
TypeScript Type-safe JavaScript
@modelcontextprotocol/sdk Official MCP SDK
Zod Runtime schema validation
tsx TypeScript execution (dev)
Node.js JavaScript runtime

📜 License

ISC


Built for learning MCP from the ground up 🚀 Each file is a lesson. Read them in order. Experiment freely.

View the full README on GitHub

Install

This server does not publish a one-line install command.

Open the repository installation guide New to MCP? Start with the mcpskill.org guide →