Episode ini membawa server MCP ke produksi: menjadikannya service HTTP dengan Express, Fastify, dan Next.js, kontainerisasi Docker, discovery via file .well-known, serta routing stateless untuk horizontal scaling dengan load balancer dan health check.

Episode 8 mengenalkan MRTR — fitur yang memungkinkan interaksi mid-call tanpa menahan streaming. Kombinasi itu (stateless core + MRTR) membuka pintu lebar untuk men-deploy server MCP sebagai service jaringan yang nyata. Di episode ini kita bawa server keluar dari terminal lokal: menjadikannya endpoint HTTP, mengemasnya dalam Docker, membiarkan klien menemukannya lewat .well-known, dan memanfaatkan sifat stateless untuk scaling horizontal.
Roadmap: mulai dari alasan pindah ke HTTP, implementasi dengan Express dan Fastify, route handler di Next.js, kontainerisasi dengan Docker, file discovery mcp.json, lalu routing stateless dengan load balancer dan health check.
Server stdio hanya bisa dipakai host yang bisa melahirkan subprocess di mesin yang sama — editor lokal, CLI agent, atau proses Node/Python kalian. Itu tidak berlaku untuk aplikasi web, agent SaaS, atau layanan yang dipakai banyak pengguna lewat internet. Untuk itu, server perlu berbicara Streamable HTTP: request/response JSON-RPC lewat POST, plus SSE untuk notifikasi dan streaming.
Info
Kabar baiknya: keputusan transport tidak mengubah logika tool kalian. Server yang sama di episode 6 bisa dipindahkan dari StdioServerTransport ke StreamableHTTPServerTransport tanpa mengubah satu pun tool.
Keuntungan utama HTTP: reachable dari mana saja, mudah diautentikasi (episode 10), dan — berkat core stateless — bisa direplikasi tanpa sesi.
Pola dasarnya sama untuk semua framework: pasang transport HTTP sekali, lalu serahkan request POST ke transport.
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "remote-tools", version: "1.0.0" });
const transport = new StreamableHTTPServerTransport({ enableJsonResponse: true });
await server.connect(transport);
app.post("/mcp", async (req, res) => {
await transport.handleRequest(req.body, req, res);
});
app.get("/healthz", (_req, res) => {
res.json({ status: "ok" });
});
app.listen(3001, () => console.log("MCP server di port 3001"));Versi Fastify hampir identik, hanya cara menulis response-nya yang berbeda:
import Fastify from "fastify";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = Fastify();
const server = new McpServer({ name: "remote-tools", version: "1.0.0" });
const transport = new StreamableHTTPServerTransport({ enableJsonResponse: true });
await server.connect(transport);
app.post("/mcp", async (request, reply) => {
await transport.handleRequest(request.body, request.raw, reply.raw);
});
app.get("/healthz", async () => ({ status: "ok" }));
await app.listen({ port: 3001 });Perhatikan tiga hal: route endpoint /mcp menerima semua request JSON-RPC, route /healthz untuk health check (dipakai di bagian routing), dan express.json() (atau middleware sejenis) wajib agar body ter-parse sebelum masuk transport.
Di Next.js App Router, endpoint MCP ditulis sebagai route handler di app/api/mcp/route.ts. Transport HTTP dibuat lazy — sekali saja — lalu diulang untuk setiap request:
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
let transport;
export async function POST(request) {
if (!transport) {
transport = new StreamableHTTPServerTransport();
await server.connect(transport);
}
return transport.handleRequest(await request.json(), request, Response);
}Catatan penting: App Router mendukung streaming, tapi tidak semua opsi SSE berlaku di serverless function dengan durasi eksekusi terbatas. Untuk skala besar, jalankan server di container (bukan serverless) supaya streaming dan MRTR bekerja dengan nyaman.
Multi-stage Dockerfile menjaga image tetap kecil: tahap build untuk compile TypeScript, tahap runtime hanya berisi artefak dan dependency produksi.
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3001
CMD ["node", "dist/server.js"]Jalankan dengan compose, lengkap dengan health check:
services:
mcp-server:
build: .
ports:
- "3001:3001"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/healthz"]
interval: 30sVariabel port dan konfigurasi lain dibaca lewat environment variable (misalnya variabel MCP_SERVER_PORT) yang dipetakan dari secrets di platform orkestrasi — bukan di-hardcode di image.
Klien bisa menemukan server MCP secara otomatis lewat URL discovery. Publikasikan file mcp.json di path /.well-known/mcp.json:
{
"mcpServers": {
"production": {
"url": "https://mcp.example.com/mcp"
}
}
}Klien cukup tahu domain (misalnya https://mcp.example.com), membaca file tersebut, lalu mengarahkan koneksi ke url yang tercantum. Ini pola yang sama dengan /.well-known/ milik OpenID Connect — familiar dan mudah diadopsi tooling.
Ini bagian paling menarik. Karena core 2026-07-28 adalah stateless, server tidak menyimpan sesi di memori — kecuali state in-flight yang sengaja dibuka lewat MRTR. Konsekuensinya: scaling horizontal tanpa sticky session, load balancer round-robin yang membiarkan request jatuh ke replica mana pun, dan health check /healthz untuk memberi tahu load balancer replica mana yang layak menerima traffic.
upstream mcp_pool {
server 10.0.0.11:3001;
server 10.0.0.12:3001;
}
server {
listen 443 ssl;
location /mcp {
proxy_pass http://mcp_pool;
proxy_http_version 1.1;
}
location /healthz {
proxy_pass http://mcp_pool;
}
}Untuk flow MRTR yang butuh follow-up diarahkan ke instance yang sama, gunakan routingId (header Mcp-Routing-Id) sebagai dasar routing di load balancer — misalnya hash berdasarkan header tersebut. Sisanya, biarkan beban tersebar merata.
Episode 9 mengubah server lokal kalian menjadi layanan produksi: endpoint Streamable HTTP di Express, Fastify, dan Next.js, image Docker yang siap naik, discovery lewat mcp.json, serta arsitektur stateless yang membuka scaling horizontal tanpa sticky session.
Inti yang harus dibawa pulang:
stdio ke HTTP cukup menukar transport./mcp untuk JSON-RPC, GET /healthz untuk kesehatan./.well-known/mcp.json memudahkan onboarding klien.Di episode 10 berikutnya kita kunci pintu yang selama ini terbuka: Authorization dengan OAuth 2.1 — peran resource server dan authorization server, header Mcp-Authorization, refresh token, PKCE, hingga mitigasi SSRF dan CRLF. Sampai jumpa!