{"id":164,"date":"2026-10-03T18:31:09","date_gmt":"2026-10-03T18:31:09","guid":{"rendered":"https:\/\/devdojo.co.in\/?p=164"},"modified":"2026-10-03T18:31:10","modified_gmt":"2026-10-03T18:31:10","slug":"build-mcp-server-typescript","status":"publish","type":"post","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/","title":{"rendered":"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)"},"content":{"rendered":"\n<style>\n.single article:has(.dd-hero) .entry-media{display:none}\n.dd-post{--dd-accent:#3178c6;--dd-accent2:#7c5cff;--dd-ink:#0b1020;--dd-soft:#f3f5fb;--dd-line:#dfe3f0}\n.dd-post .dd-hero{border-radius:16px;overflow:hidden;margin:0 0 1.5em;box-shadow:0 10px 30px rgba(11,16,32,.25)}\n.dd-post .dd-hero svg{display:block;width:100%;height:auto}\n.dd-post .ddb-node{transition:transform .25s ease,filter .25s ease;transform-box:fill-box;transform-origin:center;cursor:help}\n.dd-post .ddb-node:hover{transform:scale(1.04);filter:drop-shadow(0 0 12px rgba(124,92,255,.7))}\n.dd-post figure.dd-fig{margin:1.8em 0;text-align:center}\n.dd-post figure.dd-fig svg{width:100%;height:auto;border-radius:14px;background:#0f1430}\n.dd-post figure.dd-fig figcaption{font-size:.9em;color:#6b7280;margin-top:.5em}\n.dd-post .dd-tldr{background:linear-gradient(135deg,#eef4ff,#f4efff);border-left:5px solid var(--dd-accent2);padding:1em 1.2em;border-radius:10px;margin:1.5em 0;color:#1f2544}\n.dd-post .dd-tldr ul{margin:.4em 0 0 1.1em}\n.dd-post .dd-note{background:#fff8e6;border-left:5px solid #f4b400;padding:.8em 1em;border-radius:8px;margin:1.2em 0;color:#3d3200}\n.dd-post .dd-tip{background:#e9fbf3;border-left:5px solid #12b886;padding:.8em 1em;border-radius:8px;margin:1.2em 0;color:#0b3d2c}\n.dd-post pre{background:#0f1430;color:#e6e9ff;padding:1.1em 1.2em;border-radius:12px;overflow-x:auto;font-size:.88em;line-height:1.55;margin:1em 0 1.4em}\n.dd-post pre code{background:none;color:inherit;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.dd-post .dd-term{margin:1em 0 1.5em;border-radius:12px;overflow:hidden;background:#0f1430;box-shadow:0 8px 24px rgba(11,16,32,.25);border:1px solid #232b5c}\n.dd-post .dd-term-bar{display:flex;align-items:center;gap:7px;padding:.55em .9em;background:#1a2150;border-bottom:1px solid #232b5c}\n.dd-post .dd-term-bar i{width:12px;height:12px;border-radius:50%;background:#ff5f57;display:inline-block}\n.dd-post .dd-term-bar i:nth-child(2){background:#febc2e}.dd-post .dd-term-bar i:nth-child(3){background:#28c840}\n.dd-post .dd-term-bar span{margin-left:.6em;color:#a9b8ff;font:600 .78em ui-monospace,Menlo,monospace}\n.dd-post .dd-term pre{margin:0;border-radius:0;box-shadow:none}\n.dd-post pre,.dd-post pre code{white-space:pre!important;word-wrap:normal!important;overflow-wrap:normal!important;overflow-x:auto;line-height:1.55!important}\n.dd-post pre code{display:block;font-size:.92em}\n.dd-post .t-cmd{color:#7ee787;font-weight:600}.dd-post .t-cm{color:#7d88b8;font-style:italic}.dd-post .t-kw{color:#ff7ab2}\n.dd-post .t-doc{color:#e6e9ff}.dd-post .t-pr{color:#28c840;font-weight:700}.dd-post .t-out{color:#c7cbe0}\n.dd-post .t-json{color:#ffd580}\n.dd-post .dd-file{display:inline-block;background:#1b2250;color:#a9b8ff;font:600 .78em ui-monospace,Menlo,monospace;padding:.25em .7em;border-radius:8px 8px 0 0;margin:1em 0 -1em}\n.dd-post :not(pre)>code{background:#eef1fa;color:#3b2bb3;padding:.1em .35em;border-radius:5px;font-size:.9em}\n.dd-post table{width:100%;border-collapse:collapse;margin:1em 0 1.5em;font-size:.95em}\n.dd-post th,.dd-post td{border:1px solid var(--dd-line);padding:.6em .8em;text-align:left;vertical-align:top}\n.dd-post th{background:var(--dd-soft)}\n.dd-post .dd-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin:1.4em 0}\n.dd-post .dd-card{border-radius:14px;padding:1.1em;color:#fff;box-shadow:0 6px 18px rgba(11,16,32,.15);transition:transform .2s}\n.dd-post .dd-card:hover{transform:translateY(-4px)}\n.dd-post .dd-card h4{margin:.3em 0 .4em;color:#fff;font-size:1.15em}\n.dd-post .dd-card p{margin:0;font-size:.92em;opacity:.95}\n.dd-post .dd-card .dd-ic{font:700 1.4em ui-monospace,Menlo,monospace}\n.dd-post .dd-card small{display:block;margin-top:.6em;opacity:.8;font-family:ui-monospace,Menlo,monospace}\n.dd-post .dd-c1{background:linear-gradient(135deg,#3178c6,#4cc9f0)}\n.dd-post .dd-c2{background:linear-gradient(135deg,#12b886,#80c9a0)}\n.dd-post .dd-c3{background:linear-gradient(135deg,#7c5cff,#c77dff)}\n\/* interactive flow explorer *\/\n.dd-post .dd-flow{border:1px solid var(--dd-line);border-radius:14px;overflow:hidden;margin:1.5em 0;background:#fff}\n.dd-post .dd-flow input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-flow .dd-tabs{display:flex;flex-wrap:wrap;background:var(--dd-soft);border-bottom:1px solid var(--dd-line)}\n.dd-post .dd-flow label{flex:1 1 120px;padding:.75em .6em;text-align:center;cursor:pointer;font-weight:600;font-size:.9em;color:#4b5375;border-bottom:3px solid transparent;margin:0}\n.dd-post .dd-flow label span{display:inline-block;width:1.6em;height:1.6em;line-height:1.6em;border-radius:50%;background:#dfe3f0;color:#4b5375;margin-right:.35em;font-size:.85em}\n.dd-post .dd-flow .dd-panel{display:none;padding:1em 1.2em}\n.dd-post .dd-flow .dd-panel p{margin:.2em 0 .6em}\n.dd-post .dd-flow .dd-dir{display:inline-block;font:700 .75em ui-monospace,Menlo,monospace;padding:.2em .6em;border-radius:20px;background:#e8f0ff;color:#2457a6}\n.dd-post #dd-s1:checked~.dd-tabs label[for=dd-s1],.dd-post #dd-s2:checked~.dd-tabs label[for=dd-s2],.dd-post #dd-s3:checked~.dd-tabs label[for=dd-s3],.dd-post #dd-s4:checked~.dd-tabs label[for=dd-s4]{color:var(--dd-accent2);border-bottom-color:var(--dd-accent2);background:#fff}\n.dd-post #dd-s1:checked~.dd-tabs label[for=dd-s1] span,.dd-post #dd-s2:checked~.dd-tabs label[for=dd-s2] span,.dd-post #dd-s3:checked~.dd-tabs label[for=dd-s3] span,.dd-post #dd-s4:checked~.dd-tabs label[for=dd-s4] span{background:var(--dd-accent2);color:#fff}\n.dd-post #dd-s1:checked~.dd-panels .dd-p1,.dd-post #dd-s2:checked~.dd-panels .dd-p2,.dd-post #dd-s3:checked~.dd-panels .dd-p3,.dd-post #dd-s4:checked~.dd-panels .dd-p4{display:block}\n.dd-post details{border:1px solid var(--dd-line);border-radius:10px;padding:.7em 1em;margin:.6em 0;background:#fff}\n.dd-post details summary{cursor:pointer;font-weight:700}\n.dd-post details[open] summary{margin-bottom:.5em}\n.dd-post .dd-toc{background:var(--dd-soft);border-radius:12px;padding:1em 1.3em;margin:1.5em 0}\n.dd-post .dd-toc ol{margin:.4em 0 0 1.2em;columns:2;column-gap:2em}\n@media (max-width:640px){.dd-post .dd-toc ol{columns:1}}\n.dd-post .dd-cta{background:linear-gradient(135deg,#0b1020,#1d2560);color:#fff;border-radius:16px;padding:1.4em 1.5em;margin:2em 0}\n.dd-post .dd-cta a{color:#8ab4ff}\n<\/style>\n<div class=\"dd-post\">\n<div class=\"dd-hero\"><svg class=\"dd-banner\" viewBox=\"0 0 1200 520\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-labelledby=\"ddb-t ddb-d\">\n  <title id=\"ddb-t\">Build an MCP Server in TypeScript<\/title>\n  <desc id=\"ddb-d\">Animated diagram: an AI app sends requests to your MCP server, which calls a database, the GitHub API and local files.<\/desc>\n  <defs>\n    <linearGradient id=\"ddb-bg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n      <stop offset=\"0\" stop-color=\"#0b1020\"\/>\n      <stop offset=\"1\" stop-color=\"#151b3b\"\/>\n    <\/linearGradient>\n    <linearGradient id=\"ddb-core\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n      <stop offset=\"0\" stop-color=\"#3178c6\"\/>\n      <stop offset=\"1\" stop-color=\"#7c5cff\"\/>\n    <\/linearGradient>\n    <pattern id=\"ddb-grid\" width=\"40\" height=\"40\" patternUnits=\"userSpaceOnUse\">\n      <path d=\"M40 0H0V40\" fill=\"none\" stroke=\"#ffffff\" stroke-opacity=\".05\"\/>\n    <\/pattern>\n    <filter id=\"ddb-glow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n      <feGaussianBlur stdDeviation=\"6\" result=\"b\"\/>\n      <feMerge><feMergeNode in=\"b\"\/><feMergeNode in=\"SourceGraphic\"\/><\/feMerge>\n    <\/filter>\n  <\/defs>\n  <rect width=\"1200\" height=\"520\" fill=\"url(#ddb-bg)\"\/>\n  <rect width=\"1200\" height=\"520\" fill=\"url(#ddb-grid)\"\/>\n\n  <!-- headline -->\n  <text x=\"60\" y=\"78\" fill=\"#8ab4ff\" font-family=\"ui-monospace,Menlo,Consolas,monospace\" font-size=\"18\" letter-spacing=\"2\">DEVDOJO \u00b7 AI + BACKEND<\/text>\n  <text x=\"60\" y=\"130\" fill=\"#ffffff\" font-family=\"system-ui,-apple-system,Segoe UI,Roboto,sans-serif\" font-size=\"44\" font-weight=\"800\">Build an MCP Server in TypeScript<\/text>\n  <text x=\"60\" y=\"168\" fill=\"#c7cbe0\" font-family=\"system-ui,-apple-system,Segoe UI,Roboto,sans-serif\" font-size=\"20\">Step-by-step 2026 guide \u00b7 Official SDK v2 \u00b7 6 hands-on examples<\/text>\n\n  <!-- connections -->\n  <g fill=\"none\" stroke-width=\"2.5\" stroke-linecap=\"round\">\n    <path id=\"ddb-p0\" d=\"M300 345 H470\" stroke=\"#3a4a8a\"\/>\n    <path id=\"ddb-p1\" d=\"M730 345 C800 345 820 255 900 255\" stroke=\"#3a4a8a\"\/>\n    <path id=\"ddb-p2\" d=\"M730 345 H900\" stroke=\"#3a4a8a\"\/>\n    <path id=\"ddb-p3\" d=\"M730 345 C800 345 820 435 900 435\" stroke=\"#3a4a8a\"\/>\n  <\/g>\n  <!-- moving packets -->\n  <g filter=\"url(#ddb-glow)\">\n    <circle r=\"6\" fill=\"#4cc9f0\"><animateMotion dur=\"2.4s\" repeatCount=\"indefinite\"><mpath href=\"#ddb-p0\"\/><\/animateMotion><\/circle>\n    <circle r=\"6\" fill=\"#f72585\"><animateMotion dur=\"2.4s\" begin=\"1.2s\" repeatCount=\"indefinite\" keyPoints=\"1;0\" keyTimes=\"0;1\" calcMode=\"linear\"><mpath href=\"#ddb-p0\"\/><\/animateMotion><\/circle>\n    <circle r=\"5\" fill=\"#80ffdb\"><animateMotion dur=\"2s\" repeatCount=\"indefinite\"><mpath href=\"#ddb-p1\"\/><\/animateMotion><\/circle>\n    <circle r=\"5\" fill=\"#ffd166\"><animateMotion dur=\"2s\" begin=\".6s\" repeatCount=\"indefinite\"><mpath href=\"#ddb-p2\"\/><\/animateMotion><\/circle>\n    <circle r=\"5\" fill=\"#c77dff\"><animateMotion dur=\"2s\" begin=\"1.2s\" repeatCount=\"indefinite\"><mpath href=\"#ddb-p3\"\/><\/animateMotion><\/circle>\n  <\/g>\n  <text x=\"385\" y=\"330\" text-anchor=\"middle\" fill=\"#8f98c8\" font-family=\"ui-monospace,Menlo,Consolas,monospace\" font-size=\"13\">JSON-RPC<\/text>\n\n  <!-- AI app -->\n  <g class=\"ddb-node\">\n    <title>MCP host \/ client: Claude, VS Code, Cursor or your own app<\/title>\n    <rect x=\"60\" y=\"280\" width=\"240\" height=\"130\" rx=\"18\" fill=\"#141a36\" stroke=\"#4cc9f0\" stroke-width=\"2\"\/>\n    <circle cx=\"105\" cy=\"325\" r=\"18\" fill=\"#4cc9f0\" fill-opacity=\".18\" stroke=\"#4cc9f0\"\/>\n    <path d=\"M97 325h16M105 317v16\" stroke=\"#4cc9f0\" stroke-width=\"2.5\" stroke-linecap=\"round\"\/>\n    <text x=\"135\" y=\"321\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"20\" font-weight=\"700\">AI App<\/text>\n    <text x=\"135\" y=\"343\" fill=\"#9aa3cf\" font-family=\"system-ui,sans-serif\" font-size=\"14\">MCP client<\/text>\n    <text x=\"84\" y=\"384\" fill=\"#c7cbe0\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"13\">&#8220;Save a note\u2026&#8221;<\/text>\n  <\/g>\n\n  <!-- MCP server core -->\n  <g class=\"ddb-node ddb-core\">\n    <title>Your MCP server: tools, resources and prompts written in TypeScript<\/title>\n    <rect x=\"470\" y=\"265\" width=\"260\" height=\"160\" rx=\"22\" fill=\"url(#ddb-core)\" filter=\"url(#ddb-glow)\"\/>\n    <text x=\"600\" y=\"315\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"24\" font-weight=\"800\">Your MCP Server<\/text>\n    <text x=\"600\" y=\"340\" text-anchor=\"middle\" fill=\"#e6e9ff\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"14\">TypeScript \u00b7 SDK v2<\/text>\n    <g font-family=\"ui-monospace,Menlo,monospace\" font-size=\"13\" fill=\"#fff\">\n      <rect x=\"492\" y=\"362\" width=\"66\" height=\"36\" rx=\"8\" fill=\"#000\" fill-opacity=\".25\"\/><text x=\"525\" y=\"385\" text-anchor=\"middle\">tools<\/text>\n      <rect x=\"567\" y=\"362\" width=\"66\" height=\"36\" rx=\"8\" fill=\"#000\" fill-opacity=\".25\"\/><text x=\"600\" y=\"385\" text-anchor=\"middle\">res<\/text>\n      <rect x=\"642\" y=\"362\" width=\"66\" height=\"36\" rx=\"8\" fill=\"#000\" fill-opacity=\".25\"\/><text x=\"675\" y=\"385\" text-anchor=\"middle\">prompts<\/text>\n    <\/g>\n  <\/g>\n\n  <!-- backends -->\n  <g class=\"ddb-node\"><title>Database: notes, users, orders\u2026<\/title>\n    <rect x=\"900\" y=\"222\" width=\"240\" height=\"66\" rx=\"14\" fill=\"#141a36\" stroke=\"#80ffdb\" stroke-width=\"2\"\/>\n    <ellipse cx=\"935\" cy=\"246\" rx=\"14\" ry=\"6\" fill=\"none\" stroke=\"#80ffdb\" stroke-width=\"2\"\/>\n    <path d=\"M921 246v18c0 3.3 6.3 6 14 6s14-2.7 14-6v-18\" fill=\"none\" stroke=\"#80ffdb\" stroke-width=\"2\"\/>\n    <text x=\"965\" y=\"262\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"18\" font-weight=\"600\">Database<\/text>\n  <\/g>\n  <g class=\"ddb-node\"><title>External APIs like GitHub, Stripe or weather<\/title>\n    <rect x=\"900\" y=\"312\" width=\"240\" height=\"66\" rx=\"14\" fill=\"#141a36\" stroke=\"#ffd166\" stroke-width=\"2\"\/>\n    <text x=\"935\" y=\"352\" text-anchor=\"middle\" fill=\"#ffd166\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"20\" font-weight=\"700\">{ }<\/text>\n    <text x=\"965\" y=\"352\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"18\" font-weight=\"600\">GitHub API<\/text>\n  <\/g>\n  <g class=\"ddb-node\"><title>Local files and docs<\/title>\n    <rect x=\"900\" y=\"402\" width=\"240\" height=\"66\" rx=\"14\" fill=\"#141a36\" stroke=\"#c77dff\" stroke-width=\"2\"\/>\n    <path d=\"M924 420h14l8 8v22h-22z\" fill=\"none\" stroke=\"#c77dff\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>\n    <text x=\"965\" y=\"442\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"18\" font-weight=\"600\">Files &amp; Docs<\/text>\n  <\/g>\n<\/svg>\n<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<p>AI assistants are only as useful as the data and tools they can reach. The <strong>Model Context Protocol (MCP)<\/strong> is the open standard that lets apps like Claude, ChatGPT, VS Code and Cursor talk to your own APIs, databases and files in one consistent way. In this guide you will <strong>build an MCP server in TypeScript<\/strong> from scratch using the official SDK v2, with <strong>six hands-on examples<\/strong>: tools, error handling, a real API integration, resources, prompts, a test client, and a remote HTTP server.<\/p>\n\n<div class=\"dd-tldr\"><strong>TL;DR<\/strong>\n<ul>\n<li>Install <code>@modelcontextprotocol\/server<\/code> and <code>zod<\/code>.<\/li>\n<li>Create an <code>McpServer<\/code>, register <strong>tools<\/strong> (actions), <strong>resources<\/strong> (data) and <strong>prompts<\/strong> (templates).<\/li>\n<li>Every code snippet is a <strong>copy-paste bash command<\/strong>.<\/li>\n<li>Run it with <code>serveStdio()<\/code> locally, or over <strong>Streamable HTTP<\/strong> for remote users.<\/li>\n<li>Test with <strong>MCP Inspector<\/strong>, then plug it into Claude, VS Code or Cursor.<\/li>\n<\/ul>\n<em>Time needed: ~45 minutes. Level: beginner\u2013intermediate (basic Node.js + TypeScript).<\/em><\/div>\n\n<div class=\"dd-toc\"><strong>What you\u2019ll learn<\/strong>\n<ol>\n<li><a href=\"#what-is-mcp\">What is MCP?<\/a><\/li>\n<li><a href=\"#how-it-works\">How an MCP request works<\/a><\/li>\n<li><a href=\"#setup\">Project setup<\/a><\/li>\n<li><a href=\"#ex1\">Example 1: Notes server (tools)<\/a><\/li>\n<li><a href=\"#ex2\">Example 2: Error handling<\/a><\/li>\n<li><a href=\"#ex3\">Example 3: GitHub API tool<\/a><\/li>\n<li><a href=\"#ex4\">Example 4: Resources<\/a><\/li>\n<li><a href=\"#ex5\">Example 5: Prompts<\/a><\/li>\n<li><a href=\"#ex6\">Example 6: Test client<\/a><\/li>\n<li><a href=\"#http\">Remote HTTP server<\/a><\/li>\n<li><a href=\"#connect\">Connect to Claude, VS Code &amp; Cursor<\/a><\/li>\n<li><a href=\"#best\">Best practices &amp; FAQ<\/a><\/li>\n<\/ol><\/div>\n\n<h2 id=\"what-is-mcp\">What is the Model Context Protocol (MCP)?<\/h2>\n<p>MCP is a protocol (built on <strong>JSON-RPC 2.0<\/strong>) that defines how an AI application discovers and uses capabilities exposed by a program you write. Think of it as <strong>\u201cUSB-C for AI\u201d<\/strong>: build your server once, and every MCP-compatible app can plug into it.<\/p>\n<p>There are three roles:<\/p>\n<ul>\n<li><strong>Host<\/strong> \u2013 the AI app the user talks to (Claude Desktop, VS Code, Cursor, your own chatbot).<\/li>\n<li><strong>Client<\/strong> \u2013 a connector inside the host that keeps a 1:1 connection with one server.<\/li>\n<li><strong>Server<\/strong> \u2013 <em>your<\/em> program that exposes tools, resources and prompts.<\/li>\n<\/ul>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<figure class=\"dd-fig\">\n<svg viewBox=\"0 0 900 380\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"MCP architecture: one host with several clients, each connected to one MCP server\">\n  <rect width=\"900\" height=\"380\" fill=\"#0f1430\"\/>\n  <rect x=\"30\" y=\"40\" width=\"300\" height=\"300\" rx=\"18\" fill=\"#161d45\" stroke=\"#4cc9f0\" stroke-width=\"2\"\/>\n  <text x=\"180\" y=\"78\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"20\" font-weight=\"700\">Host (AI app)<\/text>\n  <text x=\"180\" y=\"100\" text-anchor=\"middle\" fill=\"#9aa3cf\" font-family=\"system-ui,sans-serif\" font-size=\"13\">Claude \u00b7 VS Code \u00b7 Cursor \u00b7 your app<\/text>\n  <g font-family=\"ui-monospace,Menlo,monospace\" font-size=\"15\" fill=\"#fff\">\n    <rect x=\"70\" y=\"130\" width=\"220\" height=\"50\" rx=\"10\" fill=\"#1f2a66\" stroke=\"#4cc9f0\"\/><text x=\"180\" y=\"161\" text-anchor=\"middle\">MCP Client A<\/text>\n    <rect x=\"70\" y=\"200\" width=\"220\" height=\"50\" rx=\"10\" fill=\"#1f2a66\" stroke=\"#4cc9f0\"\/><text x=\"180\" y=\"231\" text-anchor=\"middle\">MCP Client B<\/text>\n    <rect x=\"70\" y=\"270\" width=\"220\" height=\"50\" rx=\"10\" fill=\"#1f2a66\" stroke=\"#4cc9f0\"\/><text x=\"180\" y=\"301\" text-anchor=\"middle\">MCP Client C<\/text>\n  <\/g>\n  <g stroke=\"#7c5cff\" stroke-width=\"2.5\" fill=\"none\" stroke-dasharray=\"6 6\">\n    <path d=\"M290 155 H560\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n    <path d=\"M290 225 H560\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n    <path d=\"M290 295 H560\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n  <\/g>\n  <text x=\"425\" y=\"140\" text-anchor=\"middle\" fill=\"#8f98c8\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"12\">stdio<\/text>\n  <text x=\"425\" y=\"210\" text-anchor=\"middle\" fill=\"#8f98c8\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"12\">stdio<\/text>\n  <text x=\"425\" y=\"280\" text-anchor=\"middle\" fill=\"#8f98c8\" font-family=\"ui-monospace,Menlo,monospace\" font-size=\"12\">Streamable HTTP<\/text>\n  <g font-family=\"system-ui,sans-serif\" font-size=\"15\" fill=\"#fff\" font-weight=\"600\">\n    <rect x=\"560\" y=\"130\" width=\"300\" height=\"50\" rx=\"10\" fill=\"#3178c6\"\/><text x=\"710\" y=\"161\" text-anchor=\"middle\">Notes server (your code)<\/text>\n    <rect x=\"560\" y=\"200\" width=\"300\" height=\"50\" rx=\"10\" fill=\"#12b886\"\/><text x=\"710\" y=\"231\" text-anchor=\"middle\">GitHub server<\/text>\n    <rect x=\"560\" y=\"270\" width=\"300\" height=\"50\" rx=\"10\" fill=\"#7c5cff\"\/><text x=\"710\" y=\"301\" text-anchor=\"middle\">Remote company API server<\/text>\n  <\/g>\n  <text x=\"710\" y=\"78\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"20\" font-weight=\"700\">MCP Servers<\/text>\n  <text x=\"710\" y=\"100\" text-anchor=\"middle\" fill=\"#9aa3cf\" font-family=\"system-ui,sans-serif\" font-size=\"13\">local or remote<\/text>\n<\/svg>\n<figcaption>Figure 1 \u2013 One host runs many clients; each client talks to exactly one MCP server.<\/figcaption>\n<\/figure>\n\n<h3>The three building blocks<\/h3>\n<div class=\"dd-cards\">\n<div class=\"dd-card dd-c1\"><div class=\"dd-ic\">\u0192()<\/div><h4>Tools<\/h4><p>Actions the <strong>AI decides<\/strong> to call, like <code style=\"background:rgba(255,255,255,.2);color:#fff\">add-note<\/code> or <code style=\"background:rgba(255,255,255,.2);color:#fff\">get-repo<\/code>.<\/p><small>model-controlled<\/small><\/div>\n<div class=\"dd-card dd-c2\"><div class=\"dd-ic\">{ }<\/div><h4>Resources<\/h4><p>Read-only <strong>data<\/strong> the app can load as context: files, configs, database rows.<\/p><small>application-controlled<\/small><\/div>\n<div class=\"dd-card dd-c3\"><div class=\"dd-ic\">\/&gt;<\/div><h4>Prompts<\/h4><p>Reusable <strong>templates<\/strong> the user picks, often shown as slash commands.<\/p><small>user-controlled<\/small><\/div>\n<\/div>\n\n<h2>Why build your own MCP server?<\/h2>\n<ul>\n<li><strong>Give AI access to your data<\/strong> \u2013 internal APIs, product database, docs, logs, tickets.<\/li>\n<li><strong>Write once, use everywhere<\/strong> \u2013 one server works across many AI apps and agent frameworks.<\/li>\n<li><strong>Stay in control<\/strong> \u2013 you decide exactly which actions are possible and validate every input.<\/li>\n<li><strong>Great portfolio project<\/strong> \u2013 MCP skills are in demand for AI engineering and backend roles.<\/li>\n<\/ul>\n\n<h2 id=\"how-it-works\">How an MCP request works (interactive)<\/h2>\n<p>Click each step to see the actual JSON-RPC messages exchanged when a user says <em>\u201cSave a note called Sprint goals\u201d<\/em>.<\/p>\n<div class=\"dd-flow\">\n<input type=\"radio\" name=\"dd-flow\" id=\"dd-s1\" checked><input type=\"radio\" name=\"dd-flow\" id=\"dd-s2\"><input type=\"radio\" name=\"dd-flow\" id=\"dd-s3\"><input type=\"radio\" name=\"dd-flow\" id=\"dd-s4\">\n<div class=\"dd-tabs\">\n<label for=\"dd-s1\"><span>1<\/span>Initialize<\/label><label for=\"dd-s2\"><span>2<\/span>List tools<\/label><label for=\"dd-s3\"><span>3<\/span>Call tool<\/label><label for=\"dd-s4\"><span>4<\/span>Result<\/label>\n<\/div>\n<div class=\"dd-panels\">\n<div class=\"dd-panel dd-p1\"><span class=\"dd-dir\">client \u2192 server<\/span>\n<p>When the app starts your server, the client introduces itself and both sides agree on a protocol version and capabilities.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>JSON-RPC \u00b7 initialize<\/span><\/div><pre><code class=\"t-json\">{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"initialize\",\n  \"params\": {\n    \"clientInfo\": { \"name\": \"my-ai-app\", \"version\": \"1.0.0\" },\n    \"capabilities\": {}\n  }\n}<\/code><\/pre><\/div><\/div>\n<div class=\"dd-panel dd-p2\"><span class=\"dd-dir\">server \u2192 client<\/span>\n<p>The client asks <code>tools\/list<\/code>. Your server answers with each tool\u2019s name, description and JSON Schema (generated from Zod). The AI reads these descriptions to decide what to call.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>JSON-RPC \u00b7 tools\/list result<\/span><\/div><pre><code class=\"t-json\">{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"result\": {\n    \"tools\": [{\n      \"name\": \"add-note\",\n      \"description\": \"Save a new note with a title and body\",\n      \"inputSchema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"title\": { \"type\": \"string\", \"minLength\": 1 },\n          \"body\":  { \"type\": \"string\" }\n        },\n        \"required\": [\"title\", \"body\"]\n      }\n    }]\n  }\n}<\/code><\/pre><\/div><\/div>\n<div class=\"dd-panel dd-p3\"><span class=\"dd-dir\">client \u2192 server<\/span>\n<p>The model decides <code>add-note<\/code> fits the user\u2019s request and fills in the arguments. The client sends <code>tools\/call<\/code>.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>JSON-RPC \u00b7 tools\/call<\/span><\/div><pre><code class=\"t-json\">{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools\/call\",\n  \"params\": {\n    \"name\": \"add-note\",\n    \"arguments\": { \"title\": \"Sprint goals\", \"body\": \"Finish login page\" }\n  }\n}<\/code><\/pre><\/div><\/div>\n<div class=\"dd-panel dd-p4\"><span class=\"dd-dir\">server \u2192 client<\/span>\n<p>Your handler runs and returns content. The AI uses it to reply to the user: <em>\u201cDone! I saved note #1, Sprint goals.\u201d<\/em><\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>JSON-RPC \u00b7 result<\/span><\/div><pre><code class=\"t-json\">{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"Saved note #1: Sprint goals\" }],\n    \"isError\": false\n  }\n}<\/code><\/pre><\/div><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-tip\"><strong>Good news:<\/strong> the SDK handles all of this JSON-RPC plumbing for you. You only write the handler functions.<\/p>\n\n<h2 id=\"setup\">Prerequisites<\/h2>\n<ul>\n<li><strong>Node.js 20+<\/strong> (check with <code>node -v<\/code>)<\/li>\n<li>npm, pnpm or yarn<\/li>\n<li>Basic TypeScript and async\/await (new to async? read our <a href=\"https:\/\/devdojo.co.in\/index.php\/2024\/08\/12\/mastering-javascript-promises-a-guide-to-asynchronous-programming\/\">JavaScript Promises guide<\/a> first)<\/li>\n<\/ul>\n\n<h2>Step 1: Set up the project<\/h2>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 notes-mcp<\/span><\/div><pre><code><span class=\"t-cm\"># 1. Create the project folder<\/span>\n<span class=\"t-cmd\">mkdir<\/span> notes-mcp &amp;&amp; cd notes-mcp\n<span class=\"t-cmd\">npm<\/span> init -y\n\n<span class=\"t-cm\"># 2. Install the MCP server SDK (v2) + Zod for input validation<\/span>\n<span class=\"t-cmd\">npm<\/span> install @modelcontextprotocol\/server zod\n\n<span class=\"t-cm\"># 3. Dev tools: TypeScript, tsx (run .ts directly) and Node types<\/span>\n<span class=\"t-cmd\">npm<\/span> install -D typescript tsx @types\/node\n\n<span class=\"t-cm\"># 4. Create the folders we'll use<\/span>\n<span class=\"t-cmd\">mkdir<\/span> -p src\/tools<\/code><\/pre><\/div>\n<p>Every snippet in this guide is a <strong>copy-paste bash command<\/strong>: paste it into your terminal inside the <code>notes-mcp<\/code> folder and the file is created for you. Start with <code>package.json<\/code> (ES modules + handy scripts):<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 package.json<\/span><\/div><pre><code><span class=\"t-cm\"># Overwrite package.json with ES modules + handy scripts<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; package.json &lt;&lt;'EOF'\n{\n  \"name\": \"notes-mcp\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"tsx src\/index.ts\",\n    \"build\": \"tsc\",\n    \"start\": \"node dist\/index.js\",\n    \"inspect\": \"npx @modelcontextprotocol\/inspector npx tsx src\/index.ts\"\n  }\n}\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># npm init cleared the dependency list, so re-add it<\/span>\n<span class=\"t-cmd\">npm<\/span> install @modelcontextprotocol\/server zod\n<span class=\"t-cmd\">npm<\/span> install -D typescript tsx @types\/node<\/code><\/pre><\/div>\n<p>Then create <code>tsconfig.json<\/code>:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 tsconfig.json<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; tsconfig.json &lt;&lt;'EOF'\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"src\"]\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<div class=\"dd-note\"><strong>SDK v1 vs v2:<\/strong> v2 uses separate packages \u2013 <code>@modelcontextprotocol\/server<\/code> and <code>@modelcontextprotocol\/client<\/code>. Many older tutorials use <code>@modelcontextprotocol\/sdk<\/code>; that is v1. The official SDK ships a codemod to help migrate.<\/div>\n\n<p>By the end of this guide your project will look like this:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 project structure<\/span><\/div><pre><code><span class=\"t-pr\">$<\/span> <span class=\"t-cmd\">tree -I node_modules<\/span>\n<span class=\"t-out\">notes-mcp\/<\/span>\n<span class=\"t-out\">\u251c\u2500\u2500 src\/<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 index.ts          # stdio entry point<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 server.ts         # builds the McpServer and registers everything<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 store.ts          # in-memory notes store<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 resources.ts      # resources (Example 4)<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 prompts.ts        # prompts (Example 5)<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 http.ts           # remote HTTP entry point<\/span>\n<span class=\"t-out\">\u2502   \u251c\u2500\u2500 test-client.ts    # automated smoke test (Example 6)<\/span>\n<span class=\"t-out\">\u2502   \u2514\u2500\u2500 tools\/<\/span>\n<span class=\"t-out\">\u2502       \u251c\u2500\u2500 notes.ts      # add \/ list \/ search (Example 1)<\/span>\n<span class=\"t-out\">\u2502       \u251c\u2500\u2500 admin.ts      # delete \/ get with errors (Example 2)<\/span>\n<span class=\"t-out\">\u2502       \u2514\u2500\u2500 github.ts     # GitHub API tool (Example 3)<\/span>\n<span class=\"t-out\">\u251c\u2500\u2500 package.json<\/span>\n<span class=\"t-out\">\u2514\u2500\u2500 tsconfig.json<\/span><\/code><\/pre><\/div>\n\n<h2 id=\"ex1\">Example 1: A notes server with tools<\/h2>\n<p>Let\u2019s start with the classic first project: a notes server the AI can use to <strong>add, list and search<\/strong> notes. We keep the store, the tools and the server in separate files so we can reuse them for both stdio and HTTP later. Paste this whole block into your terminal:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 Example 1<\/span><\/div><pre><code><span class=\"t-cm\"># \u2500\u2500 src\/store.ts: a tiny in-memory store (swap for a database later) \u2500\u2500<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; src\/store.ts &lt;&lt;'EOF'\nexport type Note = { id: number; title: string; body: string; createdAt: string };\n\nexport const notes = new Map&lt;number, Note&gt;();\nlet nextId = 1;\n\nexport function addNote(title: string, body: string): Note {\n  const note = { id: nextId++, title, body, createdAt: new Date().toISOString() };\n  notes.set(note.id, note);\n  return note;\n}\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># \u2500\u2500 src\/tools\/notes.ts: add \/ list \/ search tools \u2500\u2500<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; src\/tools\/notes.ts &lt;&lt;'EOF'\nimport type { McpServer } from '@modelcontextprotocol\/server';\nimport * as z from 'zod\/v4';\nimport { notes, addNote } from '..\/store.js';\n\nexport function registerNoteTools(server: McpServer) {\n<span class=\"t-cm\">  \/\/ \u278a Add a note<\/span>\n  server.registerTool(\n    'add-note',\n    {\n      description: 'Save a new note with a title and body. Returns the new note id.',\n      inputSchema: z.object({\n        title: z.string().min(1).describe('Short title, e.g. \"Sprint goals\"'),\n        body: z.string().describe('The note content')\n      })\n    },\n    async ({ title, body }) =&gt; {\n      const note = addNote(title, body);\n      return { content: [{ type: 'text', text: `Saved note #${note.id}: ${title}` }] };\n    }\n  );\n\n<span class=\"t-cm\">  \/\/ \u278b List all notes<\/span>\n  server.registerTool(\n    'list-notes',\n    {\n      description: 'List all saved notes with their ids and titles',\n      inputSchema: z.object({})\n    },\n    async () =&gt; {\n      const text = notes.size\n        ? [...notes.values()].map(n =&gt; `#${n.id} ${n.title}`).join('\\n')\n        : 'No notes yet.';\n      return { content: [{ type: 'text', text }] };\n    }\n  );\n\n<span class=\"t-cm\">  \/\/ \u278c Search notes by keyword<\/span>\n  server.registerTool(\n    'search-notes',\n    {\n      description: 'Search notes by keyword in the title or body',\n      inputSchema: z.object({ query: z.string().min(1) })\n    },\n    async ({ query }) =&gt; {\n      const q = query.toLowerCase();\n      const found = [...notes.values()].filter(\n        n =&gt; n.title.toLowerCase().includes(q) || n.body.toLowerCase().includes(q)\n      );\n      const text = found.length\n        ? found.map(n =&gt; `#${n.id} ${n.title}: ${n.body}`).join('\\n')\n        : `No notes match \"${query}\".`;\n      return { content: [{ type: 'text', text }] };\n    }\n  );\n}\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># \u2500\u2500 src\/server.ts: build the server (we'll add more modules later) \u2500\u2500<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; src\/server.ts &lt;&lt;'EOF'\nimport { McpServer } from '@modelcontextprotocol\/server';\nimport { registerNoteTools } from '.\/tools\/notes.js';\n\nexport function createServer() {\n  const server = new McpServer({ name: 'notes', version: '1.0.0' });\n  registerNoteTools(server);\n  return server;\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<p>Now the stdio entry point, which is all a desktop AI app needs to launch your server:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/index.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/index.ts &lt;&lt;'EOF'\nimport { serveStdio } from '@modelcontextprotocol\/server\/stdio';\nimport { createServer } from '.\/server.js';\n\nserveStdio(() =&gt; createServer());\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># Quick check: open the Inspector and try the 3 tools<\/span>\n<span class=\"t-cmd\">npm<\/span> run inspect<\/code><\/pre><\/div>\n<p><strong>How it works:<\/strong><\/p>\n<ul>\n<li><code>McpServer<\/code> creates the server with a name and version that clients will see.<\/li>\n<li><code>registerTool(name, config, handler)<\/code> exposes a function. The <strong>description<\/strong> is crucial \u2013 the AI reads it to decide when to call your tool.<\/li>\n<li><code>inputSchema<\/code> uses <strong>Zod<\/strong>. Invalid input is rejected before your handler runs, and <code>.describe()<\/code> gives the AI extra hints for each field.<\/li>\n<li><code>serveStdio<\/code> communicates over standard input\/output \u2013 that\u2019s how local servers talk to desktop apps.<\/li>\n<\/ul>\n\n<h2 id=\"ex2\">Example 2: Handling errors the right way<\/h2>\n<p>AI models make mistakes \u2013 for example, asking to delete a note that doesn\u2019t exist. Return a <strong>helpful error<\/strong> so the model can recover instead of failing silently. There are two ways:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/tools\/admin.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/tools\/admin.ts &lt;&lt;'EOF'\nimport type { McpServer } from '@modelcontextprotocol\/server';\nimport * as z from 'zod\/v4';\nimport { notes } from '..\/store.js';\n\nexport function registerAdminTools(server: McpServer) {\n<span class=\"t-cm\">  \/\/ \u278d Delete a note \u2013 Option A: return isError with a helpful message<\/span>\n  server.registerTool(\n    'delete-note',\n    {\n      description: 'Delete a note by its numeric id',\n      inputSchema: z.object({ id: z.number().int().positive() })\n    },\n    async ({ id }) =&gt; {\n      if (!notes.has(id)) {\n        return {\n          content: [{\n            type: 'text',\n            text: `No note with id ${id}. Known ids: ${[...notes.keys()].join(', ') || 'none'}`\n          }],\n          isError: true\n        };\n      }\n      notes.delete(id);\n      return { content: [{ type: 'text', text: `Deleted note #${id}` }] };\n    }\n  );\n\n<span class=\"t-cm\">  \/\/ \u278e Get one note \u2013 Option B: just throw; the SDK turns it into an isError result<\/span>\n  server.registerTool(\n    'get-note',\n    {\n      description: 'Get the full content of one note by id',\n      inputSchema: z.object({ id: z.number().int().positive() })\n    },\n    async ({ id }) =&gt; {\n      const note = notes.get(id);\n      if (!note) throw new Error(`Note #${id} not found`);\n      return { content: [{ type: 'text', text: `${note.title}\\n\\n${note.body}` }] };\n    }\n  );\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<p class=\"dd-tip\"><strong>Tip:<\/strong> Listing the valid ids in the error (like Option A) lets the AI fix its own mistake on the next try. Small detail, big improvement.<\/p>\n\n<h2 id=\"ex3\">Example 3: Calling a real API (GitHub)<\/h2>\n<p>Most useful MCP servers wrap an existing API. Here\u2019s a tool that fetches live stats for any public GitHub repository. Node 20+ has <code>fetch<\/code> built in, so no extra packages are needed.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/tools\/github.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/tools\/github.ts &lt;&lt;'EOF'\nimport type { McpServer } from '@modelcontextprotocol\/server';\nimport * as z from 'zod\/v4';\n\nexport function registerGithubTools(server: McpServer) {\n<span class=\"t-cm\">  \/\/ \u278f Get GitHub repository info<\/span>\n  server.registerTool(\n    'get-github-repo',\n    {\n      description: 'Get stars, forks, open issues and language for a public GitHub repo',\n      inputSchema: z.object({\n        owner: z.string().describe('Repo owner, e.g. \"facebook\"'),\n        repo: z.string().describe('Repo name, e.g. \"react\"')\n      })\n    },\n    async ({ owner, repo }) =&gt; {\n      const headers: Record&lt;string, string&gt; = {\n        'Accept': 'application\/vnd.github+json',\n        'User-Agent': 'notes-mcp'\n      };\n<span class=\"t-cm\">      \/\/ Optional: higher rate limits with a token from your environment<\/span>\n      if (process.env.GITHUB_TOKEN) {\n        headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;\n      }\n\n      const res = await fetch(`https:\/\/api.github.com\/repos\/${owner}\/${repo}`, { headers });\n      if (res.status === 404) throw new Error(`Repository ${owner}\/${repo} not found`);\n      if (!res.ok) throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);\n\n      const data = await res.json();\n      const summary = [\n        `\ud83d\udce6 ${data.full_name}`,\n        `${data.description ?? 'No description'}`,\n        `\u2b50 Stars: ${data.stargazers_count}`,\n        `\ud83c\udf74 Forks: ${data.forks_count}`,\n        `\ud83d\udc1e Open issues: ${data.open_issues_count}`,\n        `\ud83d\udcbb Language: ${data.language ?? 'n\/a'}`,\n        `\ud83d\udd17 ${data.html_url}`\n      ].join('\\n');\n\n      return { content: [{ type: 'text', text: summary }] };\n    }\n  );\n}\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># Optional: export a token for higher GitHub rate limits<\/span>\n<span class=\"t-cmd\">export<\/span> GITHUB_TOKEN=ghp_your_token_here<\/code><\/pre><\/div>\n<p>Now you can ask your AI: <em>\u201cCompare the stars of facebook\/react and vuejs\/core\u201d<\/em> \u2013 it will call the tool twice and compare the results for you.<\/p>\n<div class=\"dd-note\"><strong>Security:<\/strong> never hard-code API keys. Read them from environment variables (<code>process.env<\/code>) and pass them in your client config (shown later).<\/div>\n\n<h2 id=\"ex4\">Example 4: Exposing data with resources<\/h2>\n<p><strong>Resources<\/strong> are read-only data the AI app can load as context \u2013 without the model needing to \u201ccall\u201d anything. Use a fixed URI for single items, or a <code>ResourceTemplate<\/code> for dynamic ones.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/resources.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/resources.ts &lt;&lt;'EOF'\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol\/server';\nimport { notes } from '.\/store.js';\n\nexport function registerResources(server: McpServer) {\n<span class=\"t-cm\">  \/\/ Static resource: app configuration<\/span>\n  server.registerResource(\n    'config',\n    'config:\/\/app',\n    {\n      title: 'Application Config',\n      description: 'Current notes app settings',\n      mimeType: 'text\/plain'\n    },\n    async uri =&gt; ({\n      contents: [{ uri: uri.href, text: 'max_notes=500\\ndefault_tag=general' }]\n    })\n  );\n\n<span class=\"t-cm\">  \/\/ Dynamic resource: any single note by id, e.g. notes:\/\/3<\/span>\n  server.registerResource(\n    'note',\n    new ResourceTemplate('notes:\/\/{id}', {\n<span class=\"t-cm\">      \/\/ list lets clients discover every available note<\/span>\n      list: async () =&gt; ({\n        resources: [...notes.values()].map(n =&gt; ({\n          uri: `notes:\/\/${n.id}`,\n          name: n.title\n        }))\n      })\n    }),\n    {\n      description: 'A single note as JSON',\n      mimeType: 'application\/json'\n    },\n    async (uri, { id }) =&gt; {\n      const note = notes.get(Number(id));\n      if (!note) throw new Error(`Note ${id} not found`);\n      return {\n        contents: [{ uri: uri.href, mimeType: 'application\/json', text: JSON.stringify(note, null, 2) }]\n      };\n    }\n  );\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<details><summary>\ud83d\udd12 Bonus: safely serving files from a docs folder<\/summary>\n<p>If a resource reads files from disk, always make sure the path can\u2019t escape your folder (a \u201cpath traversal\u201d attack like <code>..\/..\/.env<\/code>):<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/docs-resource.ts<\/span><\/div><pre><code><span class=\"t-cmd\">mkdir<\/span> -p docs &amp;&amp; echo \"# Hello from the docs folder\" &gt; docs\/intro.md\n\n<span class=\"t-cmd\">cat<\/span> &gt; src\/docs-resource.ts &lt;&lt;'EOF'\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol\/server';\nimport { readFile, realpath } from 'node:fs\/promises';\nimport path from 'node:path';\n\nconst DOCS_ROOT = path.resolve('.\/docs');\n\nexport function registerDocsResource(server: McpServer) {\n  server.registerResource(\n    'doc',\n    new ResourceTemplate('docs:\/\/{file}', { list: undefined }),\n    { description: 'A markdown page from the docs directory', mimeType: 'text\/markdown' },\n    async (uri, { file }) =&gt; {\n      const requested = await realpath(path.join(DOCS_ROOT, String(file)));\n      if (!requested.startsWith(DOCS_ROOT + path.sep)) {\n        throw new Error(`${uri.href} resolves outside the docs root`);\n      }\n      return { contents: [{ uri: uri.href, text: await readFile(requested, 'utf8') }] };\n    }\n  );\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<\/details>\n\n<h2 id=\"ex5\">Example 5: Reusable prompts<\/h2>\n<p><strong>Prompts<\/strong> are templates users pick from a menu or slash command in the AI app. They\u2019re perfect for repeatable workflows like code review or summarising notes.<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/prompts.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/prompts.ts &lt;&lt;'EOF'\nimport type { McpServer } from '@modelcontextprotocol\/server';\nimport * as z from 'zod\/v4';\nimport { notes } from '.\/store.js';\n\nexport function registerPrompts(server: McpServer) {\n<span class=\"t-cm\">  \/\/ Prompt 1: code review<\/span>\n  server.registerPrompt(\n    'review-code',\n    {\n      title: 'Code Review',\n      description: 'Review code for bugs, performance and best practices',\n      argsSchema: z.object({\n        code: z.string().describe('The code to review'),\n        language: z.string().optional().describe('e.g. TypeScript, Python')\n      })\n    },\n    ({ code, language }) =&gt; ({\n      messages: [{\n        role: 'user' as const,\n        content: {\n          type: 'text' as const,\n          text: `You are a senior ${language ?? ''} developer. Review this code. ` +\n                `List bugs, performance issues and improvements, with fixed code:\\n\\n${code}`\n        }\n      }]\n    })\n  );\n\n<span class=\"t-cm\">  \/\/ Prompt 2: summarise all notes<\/span>\n  server.registerPrompt(\n    'summarize-notes',\n    {\n      title: 'Summarize my notes',\n      description: 'Create a short summary and action items from all saved notes',\n      argsSchema: z.object({})\n    },\n    () =&gt; ({\n      messages: [{\n        role: 'user' as const,\n        content: {\n          type: 'text' as const,\n          text: 'Summarize these notes in 5 bullet points, then list action items:\\n\\n' +\n                [...notes.values()].map(n =&gt; `- ${n.title}: ${n.body}`).join('\\n')\n        }\n      }]\n    })\n  );\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<h3>Wire everything together<\/h3>\n<p>Now register every module in <code>server.ts<\/code> and type-check the project:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/server.ts (final)<\/span><\/div><pre><code><span class=\"t-cm\"># Wire every module into the server<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; src\/server.ts &lt;&lt;'EOF'\nimport { McpServer } from '@modelcontextprotocol\/server';\nimport { registerNoteTools } from '.\/tools\/notes.js';\nimport { registerAdminTools } from '.\/tools\/admin.js';\nimport { registerGithubTools } from '.\/tools\/github.js';\nimport { registerResources } from '.\/resources.js';\nimport { registerDocsResource } from '.\/docs-resource.js';\nimport { registerPrompts } from '.\/prompts.js';\n\nexport function createServer() {\n  const server = new McpServer({ name: 'notes', version: '1.0.0' });\n\n  registerNoteTools(server);   <span class=\"t-cm\"> \/\/ Example 1<\/span>\n  registerAdminTools(server);  <span class=\"t-cm\"> \/\/ Example 2<\/span>\n  registerGithubTools(server); <span class=\"t-cm\"> \/\/ Example 3<\/span>\n  registerResources(server);   <span class=\"t-cm\"> \/\/ Example 4<\/span>\n  registerDocsResource(server);<span class=\"t-cm\"> \/\/ Example 4 (bonus)<\/span>\n  registerPrompts(server);     <span class=\"t-cm\"> \/\/ Example 5<\/span>\n\n  return server;\n}\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># Type-check everything<\/span>\n<span class=\"t-cmd\">npx<\/span> tsc --noEmit &amp;&amp; echo '\u2705 All good'<\/code><\/pre><\/div>\n\n<h2>Test visually with MCP Inspector<\/h2>\n<p>The <strong>MCP Inspector<\/strong> is the official browser-based tool for testing servers without any AI app:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 MCP Inspector<\/span><\/div><pre><code><span class=\"t-cmd\">npm<\/span> run inspect\n<span class=\"t-cm\"># same as:<\/span>\n<span class=\"t-cmd\">npx<\/span> @modelcontextprotocol\/inspector npx tsx src\/index.ts<\/code><\/pre><\/div>\n<ol>\n<li>Open the URL printed in your terminal and click <strong>Connect<\/strong>.<\/li>\n<li>Open the <strong>Tools<\/strong> tab \u2192 run <code>add-note<\/code>, then <code>list-notes<\/code>.<\/li>\n<li>Try <code>delete-note<\/code> with id <code>99<\/code> to see your error message.<\/li>\n<li>Open the <strong>Resources<\/strong> and <strong>Prompts<\/strong> tabs to check those too.<\/li>\n<\/ol>\n\n<h2 id=\"ex6\">Example 6: An automated test client<\/h2>\n<p>Clicking around is great, but an automated script catches regressions. Install the client package and create a quick smoke test:<\/p>\n\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/test-client.ts<\/span><\/div><pre><code><span class=\"t-cmd\">npm<\/span> install @modelcontextprotocol\/client\n\n<span class=\"t-cmd\">cat<\/span> &gt; src\/test-client.ts &lt;&lt;'EOF'\nimport { Client } from '@modelcontextprotocol\/client';\nimport { StdioClientTransport } from '@modelcontextprotocol\/client\/stdio';\n\nconst client = new Client({ name: 'smoke-test', version: '1.0.0' });\nconst transport = new StdioClientTransport({ command: 'npx', args: ['tsx', 'src\/index.ts'] });\n\nawait client.connect(transport);\nconsole.log('Connected to', client.getServerVersion());\n\n<span class=\"t-cm\">\/\/ 1. Which tools does the server expose?<\/span>\nconst { tools } = await client.listTools();\nconsole.log('Tools:', tools.map(t =&gt; t.name));\n\n<span class=\"t-cm\">\/\/ 2. Call a tool<\/span>\nconst added = await client.callTool({\n  name: 'add-note',\n  arguments: { title: 'Test', body: 'Hello from the test client' }\n});\nconsole.log(added.content);\n\n<span class=\"t-cm\">\/\/ 3. Read a resource<\/span>\nconst { contents } = await client.readResource({ uri: 'notes:\/\/1' });\nconsole.log(contents[0]);\n\nawait client.close();\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 output<\/span><\/div><pre><code><span class=\"t-pr\">$<\/span> <span class=\"t-cmd\">npx tsx src\/test-client.ts<\/span>\n<span class=\"t-out\">Connected to { name: 'notes', version: '1.0.0' }<\/span>\n<span class=\"t-out\">Tools: [ 'add-note', 'list-notes', 'search-notes', 'delete-note', 'get-note', 'get-github-repo' ]<\/span>\n<span class=\"t-out\">[ { type: 'text', text: 'Saved note #1: Test' } ]<\/span>\n<span class=\"t-out\">{ uri: 'notes:\/\/1', mimeType: 'application\/json', text: '{ \"id\": 1, \"title\": \"Test\", ... }' }<\/span><\/code><\/pre><\/div>\n<p class=\"dd-tip\"><strong>Tip:<\/strong> Run this in CI (GitHub Actions) on every push so you never ship a broken server.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"http\">Going remote: Streamable HTTP server<\/h2>\n<p>stdio is perfect for local use, but if you want <strong>other people<\/strong> (or a web app) to use your server, run it over <strong>Streamable HTTP<\/strong>. Because we kept <code>createServer()<\/code> separate, this is only a few lines:<\/p>\n\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/http.ts<\/span><\/div><pre><code><span class=\"t-cmd\">npm<\/span> install @modelcontextprotocol\/express @modelcontextprotocol\/node express\n\n<span class=\"t-cmd\">cat<\/span> &gt; src\/http.ts &lt;&lt;'EOF'\nimport { createMcpExpressApp } from '@modelcontextprotocol\/express';\nimport { toNodeHandler } from '@modelcontextprotocol\/node';\nimport { createMcpHandler } from '@modelcontextprotocol\/server';\nimport { createServer } from '.\/server.js';\n\nconst handler = createMcpHandler(() =&gt; createServer());\n\nconst app = createMcpExpressApp();<span class=\"t-cm\"> \/\/ adds JSON parsing + DNS-rebinding protection<\/span>\nconst node = toNodeHandler(handler);\n\napp.all('\/mcp', (req, res) =&gt; void node(req, res, req.body));\n\nconst PORT = Number(process.env.PORT ?? 3000);\napp.listen(PORT, () =&gt; console.error(`MCP server on http:\/\/localhost:${PORT}\/mcp`));\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># Start the remote server<\/span>\n<span class=\"t-cmd\">npx<\/span> tsx src\/http.ts<\/code><\/pre><\/div>\n<p>Test it with the Inspector by choosing <strong>Streamable HTTP<\/strong> and entering <code>http:\/\/localhost:3000\/mcp<\/code>, or connect from code:<\/p>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 src\/http-client.ts<\/span><\/div><pre><code><span class=\"t-cmd\">cat<\/span> &gt; src\/http-client.ts &lt;&lt;'EOF'\nimport { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol\/client';\n\nconst client = new Client({ name: 'my-client', version: '1.0.0' });\nawait client.connect(new StreamableHTTPClientTransport(new URL('http:\/\/localhost:3000\/mcp')));\n\nconst { tools } = await client.listTools();\nconsole.log('Remote tools:', tools.map(t =&gt; t.name));\nawait client.close();\n<span class=\"t-kw\">EOF<\/span>\n\n<span class=\"t-cm\"># In a second terminal (while src\/http.ts is running)<\/span>\n<span class=\"t-cmd\">npx<\/span> tsx src\/http-client.ts<\/code><\/pre><\/div>\n\n<figure class=\"dd-fig\">\n<svg viewBox=\"0 0 900 300\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"stdio versus Streamable HTTP comparison\">\n  <rect width=\"900\" height=\"300\" fill=\"#0f1430\"\/>\n  <line x1=\"450\" y1=\"30\" x2=\"450\" y2=\"270\" stroke=\"#2a3370\" stroke-width=\"2\" stroke-dasharray=\"6 6\"\/>\n  <text x=\"225\" y=\"50\" text-anchor=\"middle\" fill=\"#4cc9f0\" font-family=\"system-ui,sans-serif\" font-size=\"22\" font-weight=\"800\">stdio (local)<\/text>\n  <rect x=\"60\" y=\"80\" width=\"330\" height=\"130\" rx=\"16\" fill=\"none\" stroke=\"#4cc9f0\" stroke-width=\"2\" stroke-dasharray=\"4 4\"\/>\n  <text x=\"225\" y=\"102\" text-anchor=\"middle\" fill=\"#9aa3cf\" font-family=\"system-ui,sans-serif\" font-size=\"13\">Your laptop<\/text>\n  <rect x=\"85\" y=\"120\" width=\"120\" height=\"60\" rx=\"10\" fill=\"#1f2a66\"\/><text x=\"145\" y=\"156\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"15\">AI App<\/text>\n  <rect x=\"245\" y=\"120\" width=\"120\" height=\"60\" rx=\"10\" fill=\"#3178c6\"\/><text x=\"305\" y=\"156\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"15\">MCP Server<\/text>\n  <path d=\"M205 150 H245\" stroke=\"#4cc9f0\" stroke-width=\"3\"\/>\n  <text x=\"225\" y=\"245\" text-anchor=\"middle\" fill=\"#c7cbe0\" font-family=\"system-ui,sans-serif\" font-size=\"14\">App spawns server as a child process<\/text>\n  <text x=\"675\" y=\"50\" text-anchor=\"middle\" fill=\"#c77dff\" font-family=\"system-ui,sans-serif\" font-size=\"22\" font-weight=\"800\">Streamable HTTP (remote)<\/text>\n  <rect x=\"490\" y=\"120\" width=\"110\" height=\"60\" rx=\"10\" fill=\"#1f2a66\"\/><text x=\"545\" y=\"156\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"14\">User A<\/text>\n  <rect x=\"490\" y=\"200\" width=\"110\" height=\"50\" rx=\"10\" fill=\"#1f2a66\"\/><text x=\"545\" y=\"231\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"14\">User B<\/text>\n  <rect x=\"490\" y=\"70\" width=\"110\" height=\"40\" rx=\"10\" fill=\"#1f2a66\"\/><text x=\"545\" y=\"96\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"14\">Web app<\/text>\n  <path d=\"M700 110 a30 30 0 0 1 60 -10 a25 25 0 0 1 60 15 a22 22 0 0 1 -5 44 h-110 a25 25 0 0 1 -5 -49z\" fill=\"#7c5cff\"\/>\n  <text x=\"755\" y=\"140\" text-anchor=\"middle\" fill=\"#fff\" font-family=\"system-ui,sans-serif\" font-size=\"14\" font-weight=\"700\">\/mcp<\/text>\n  <g stroke=\"#c77dff\" stroke-width=\"2.5\" fill=\"none\" stroke-dasharray=\"6 6\">\n    <path d=\"M600 90 L700 125\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n    <path d=\"M600 150 L700 140\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n    <path d=\"M600 225 L705 155\"><animate attributeName=\"stroke-dashoffset\" from=\"24\" to=\"0\" dur=\"1s\" repeatCount=\"indefinite\"\/><\/path>\n  <\/g>\n  <text x=\"675\" y=\"285\" text-anchor=\"middle\" fill=\"#c7cbe0\" font-family=\"system-ui,sans-serif\" font-size=\"14\">Many clients connect over the internet (add OAuth)<\/text>\n<\/svg>\n<figcaption>Figure 2 \u2013 stdio vs Streamable HTTP transport.<\/figcaption>\n<\/figure>\n<table>\n<thead><tr><th><\/th><th>stdio<\/th><th>Streamable HTTP<\/th><\/tr><\/thead>\n<tbody>\n<tr><td><strong>Runs<\/strong><\/td><td>On the user\u2019s machine<\/td><td>On your server \/ cloud (Vercel, Railway, Render, AWS\u2026)<\/td><\/tr>\n<tr><td><strong>Users<\/strong><\/td><td>One<\/td><td>Many<\/td><\/tr>\n<tr><td><strong>Best for<\/strong><\/td><td>Local files, dev tools, personal automation<\/td><td>SaaS products, team tools, public APIs<\/td><\/tr>\n<tr><td><strong>Auth<\/strong><\/td><td>Not needed<\/td><td>OAuth strongly recommended<\/td><\/tr>\n<tr><td><strong>Logging<\/strong><\/td><td>Only <code>console.error<\/code> (stdout is the protocol)<\/td><td>Any logger<\/td><\/tr>\n<\/tbody>\n<\/table>\n\n<h2 id=\"connect\">Connect your MCP server to AI apps<\/h2>\n<p>These commands build the project and write each app\u2019s config with the correct <strong>absolute path<\/strong> filled in automatically via <code>$(pwd)<\/code>. Run them from inside the <code>notes-mcp<\/code> folder.<\/p>\n<h3>Claude Desktop<\/h3>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 Claude Desktop (macOS)<\/span><\/div><pre><code><span class=\"t-cmd\">npm<\/span> run build  <span class=\"t-cm\"> # creates dist\/index.js<\/span>\n\nCONFIG=\"$HOME\/Library\/Application Support\/Claude\/claude_desktop_config.json\"\n<span class=\"t-cmd\">mkdir<\/span> -p \"$(dirname \"$CONFIG\")\"\n<span class=\"t-cmd\">[<\/span> -f \"$CONFIG\" ] &amp;&amp; cp \"$CONFIG\" \"$CONFIG.backup\"  <span class=\"t-cm\"> # keep a backup!<\/span>\n\n<span class=\"t-cm\"># \u26a0\ufe0f This replaces the file. If you already have other servers, merge by hand.<\/span>\n<span class=\"t-cmd\">cat<\/span> &gt; \"$CONFIG\" &lt;&lt;EOF\n{\n  \"mcpServers\": {\n    \"notes\": {\n      \"command\": \"node\",\n      \"args\": [\"$(pwd)\/dist\/index.js\"],\n      \"env\": { \"GITHUB_TOKEN\": \"your-token-here\" }\n    }\n  }\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<h3>VS Code (GitHub Copilot agent mode)<\/h3>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 VS Code<\/span><\/div><pre><code><span class=\"t-cmd\">mkdir<\/span> -p .vscode\n<span class=\"t-cmd\">cat<\/span> &gt; .vscode\/mcp.json &lt;&lt;EOF\n{\n  \"servers\": {\n    \"notes\": {\n      \"type\": \"stdio\",\n      \"command\": \"node\",\n      \"args\": [\"$(pwd)\/dist\/index.js\"]\n    }\n  }\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<h3>Cursor<\/h3>\n<div class=\"dd-term\"><div class=\"dd-term-bar\"><i><\/i><i><\/i><i><\/i><span>bash \u2014 Cursor<\/span><\/div><pre><code><span class=\"t-cmd\">mkdir<\/span> -p .cursor\n<span class=\"t-cmd\">cat<\/span> &gt; .cursor\/mcp.json &lt;&lt;EOF\n{\n  \"mcpServers\": {\n    \"notes\": {\n      \"command\": \"node\",\n      \"args\": [\"$(pwd)\/dist\/index.js\"]\n    }\n  }\n}\n<span class=\"t-kw\">EOF<\/span><\/code><\/pre><\/div>\n<p>Restart the app and try prompts like:<\/p>\n<ul>\n<li><em>\u201cSave a note called Sprint goals: finish the login page and write tests.\u201d<\/em><\/li>\n<li><em>\u201cSearch my notes for login.\u201d<\/em><\/li>\n<li><em>\u201cHow many stars does vercel\/next.js have? Save the answer as a note.\u201d<\/em><\/li>\n<\/ul>\n\n<h2 id=\"best\">Best practices for production MCP servers<\/h2>\n<ol>\n<li><strong>Write tool descriptions for the AI.<\/strong> Say what it does, when to use it, and what it returns.<\/li>\n<li><strong>Keep tools small and focused.<\/strong> <code>search-notes<\/code> + <code>add-note<\/code> beats one giant <code>notes<\/code> tool with a <code>mode<\/code> flag.<\/li>\n<li><strong>Use clear, consistent names<\/strong> like <code>verb-noun<\/code> (<code>get-repo<\/code>, <code>create-issue<\/code>).<\/li>\n<li><strong>Validate everything<\/strong> with Zod \u2013 never trust inputs, even from an AI.<\/li>\n<li><strong>Return helpful errors<\/strong> with hints to recover (\u201cKnown ids: 1, 2, 5\u201d).<\/li>\n<li><strong>Protect dangerous actions.<\/strong> Avoid exposing deletes, payments or emails without confirmation.<\/li>\n<li><strong>Keep secrets in environment variables<\/strong> and never return them in tool results.<\/li>\n<li><strong>Keep outputs short.<\/strong> Huge responses waste the model\u2019s context window \u2013 paginate or summarise.<\/li>\n<li><strong>Add auth for remote servers<\/strong> and rate-limit public endpoints.<\/li>\n<\/ol>\n\n<h2>Common errors and fixes<\/h2>\n<table>\n<thead><tr><th>Error \/ symptom<\/th><th>Fix<\/th><\/tr><\/thead>\n<tbody>\n<tr><td><code>Cannot use import statement outside a module<\/code><\/td><td>Add <code>\"type\": \"module\"<\/code> to <code>package.json<\/code>.<\/td><\/tr>\n<tr><td><code>Cannot find name 'process'<\/code> (or <code>node:fs<\/code>)<\/td><td>Run <code>npm i -D @types\/node<\/code> and add <code>\"types\": [\"node\"]<\/code> to <code>tsconfig.json<\/code>.<\/td><\/tr>\n<tr><td><code>ERR_MODULE_NOT_FOUND .\/server<\/code><\/td><td>With NodeNext, import local files with <code>.js<\/code>: <code>'.\/server.js'<\/code>.<\/td><\/tr>\n<tr><td>Server not showing in the AI app<\/td><td>Use absolute paths, check the JSON is valid, fully quit and restart the app.<\/td><\/tr>\n<tr><td>Connection breaks randomly (stdio)<\/td><td>Don\u2019t use <code>console.log<\/code> \u2013 it writes to stdout. Use <code>console.error<\/code>.<\/td><\/tr>\n<tr><td>AI never calls your tool<\/td><td>Improve the description and field <code>.describe()<\/code> hints; mention typical user phrases.<\/td><\/tr>\n<tr><td>Code from an old tutorial doesn\u2019t compile<\/td><td>It\u2019s probably SDK v1 (<code>@modelcontextprotocol\/sdk<\/code>). Use the v2 imports shown here.<\/td><\/tr>\n<\/tbody>\n<\/table>\n\n<h2>Frequently Asked Questions<\/h2>\n<details><summary>Is MCP only for Claude?<\/summary><p>No. MCP is an open standard supported by many AI apps, IDEs (VS Code, Cursor, JetBrains) and agent frameworks.<\/p><\/details>\n<details><summary>Can I build an MCP server in Python?<\/summary><p>Yes. There\u2019s an official Python SDK too. Tools, resources and prompts work the same way \u2013 only the syntax changes.<\/p><\/details>\n<details><summary>What\u2019s the difference between MCP SDK v1 and v2?<\/summary><p>v2 implements the newer MCP spec and splits the SDK into <code>@modelcontextprotocol\/server<\/code> and <code>@modelcontextprotocol\/client<\/code>, plus adapters like <code>@modelcontextprotocol\/express<\/code>. v1 used one <code>@modelcontextprotocol\/sdk<\/code> package.<\/p><\/details>\n<details><summary>Tools vs resources \u2013 which should I use?<\/summary><p>Use a <strong>tool<\/strong> when the AI should decide to take an action or fetch something on demand. Use a <strong>resource<\/strong> for data the app or user attaches as context, like a file or a record.<\/p><\/details>\n<details><summary>Do I need a database?<\/summary><p>Not to start \u2013 this tutorial uses an in-memory <code>Map<\/code>. For real use, connect PostgreSQL, MongoDB, Supabase or SQLite inside your handlers.<\/p><\/details>\n<details><summary>Where can I deploy a remote MCP server?<\/summary><p>Anywhere that runs Node.js: Vercel, Railway, Render, Fly.io, AWS, or your own VPS. Put it behind HTTPS and add authentication.<\/p><\/details>\n\n<h2>Conclusion<\/h2>\n<p>You built a complete MCP server in TypeScript: <strong>six tools<\/strong> (including a live GitHub integration), <strong>resources<\/strong>, <strong>prompts<\/strong>, proper <strong>error handling<\/strong>, an <strong>automated test client<\/strong> and a <strong>remote HTTP version<\/strong> \u2013 and you connected it to Claude, VS Code and Cursor.<\/p>\n<p><strong>Next challenges to try:<\/strong><\/p>\n<ul>\n<li>Swap the in-memory <code>Map<\/code> for PostgreSQL or Supabase.<\/li>\n<li>Add a <code>create-github-issue<\/code> tool (with a confirmation step!).<\/li>\n<li>Deploy the HTTP server and share it with your team.<\/li>\n<\/ul>\n<div class=\"dd-cta\">\n<strong>\ud83d\ude80 New posts every day on DevDojo<\/strong><br>\nAI, frontend, backend and career guides for developers. Bookmark <a href=\"https:\/\/devdojo.co.in\">devdojo.co.in<\/a> and level up daily. Brush up on async code with our <a href=\"https:\/\/devdojo.co.in\/index.php\/2024\/08\/12\/mastering-javascript-promises-a-guide-to-asynchronous-programming\/\">JavaScript Promises guide<\/a>.\n<\/div>\n<p><small>Reference: <a href=\"https:\/\/ts.sdk.modelcontextprotocol.io\/v2\/\" target=\"_blank\" rel=\"noopener\">Official MCP TypeScript SDK documentation<\/a><\/small><\/p>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.<\/p>\n","protected":false},"author":1,"featured_media":158,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38,7,8],"tags":[42,44,39,40,43,41],"class_list":["post-164","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-backend","category-nodejs","tag-ai-agents","tag-llm","tag-mcp","tag-model-context-protocol","tag-node-js","tag-typescript"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Lokendra\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"DevDojo - Empowering Developers\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"How to Build an MCP Server in TypeScript (2026 Guide)\" \/>\n\t\t<meta property=\"og:description\" content=\"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-10-03T18:31:09+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-10-03T18:31:10+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"How to Build an MCP Server in TypeScript (2026 Guide)\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#blogposting\",\"name\":\"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo\",\"headline\":\"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)\",\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/build-mcp-server-typescript.jpg\",\"width\":1200,\"height\":630,\"caption\":\"Build an MCP Server in TypeScript \\u2013 diagram of an AI app connecting to an MCP server with database, GitHub API and files\"},\"datePublished\":\"2026-10-03T18:31:09+00:00\",\"dateModified\":\"2026-10-03T18:31:10+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#webpage\"},\"articleSection\":\"AI, Backend, NodeJS, AI Agents, LLM, MCP, Model Context Protocol, Node.js, TypeScript\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"name\":\"Backend\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"position\":2,\"name\":\"Backend\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/nodejs\\\/#listItem\",\"name\":\"NodeJS\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/nodejs\\\/#listItem\",\"position\":3,\"name\":\"NodeJS\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/nodejs\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#listItem\",\"name\":\"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"name\":\"Backend\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#listItem\",\"position\":4,\"name\":\"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/nodejs\\\/#listItem\",\"name\":\"NodeJS\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/\",\"name\":\"Lokendra\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Lokendra\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#webpage\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/\",\"name\":\"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo\",\"description\":\"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/build-mcp-server-typescript.jpg\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#mainImage\",\"width\":1200,\"height\":630,\"caption\":\"Build an MCP Server in TypeScript \\u2013 diagram of an AI app connecting to an MCP server with database, GitHub API and files\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/03\\\/build-mcp-server-typescript\\\/#mainImage\"},\"datePublished\":\"2026-10-03T18:31:09+00:00\",\"dateModified\":\"2026-10-03T18:31:10+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo","description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.","canonical_url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#blogposting","name":"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo","headline":"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)","author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/build-mcp-server-typescript.jpg","width":1200,"height":630,"caption":"Build an MCP Server in TypeScript \u2013 diagram of an AI app connecting to an MCP server with database, GitHub API and files"},"datePublished":"2026-10-03T18:31:09+00:00","dateModified":"2026-10-03T18:31:10+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#webpage"},"isPartOf":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#webpage"},"articleSection":"AI, Backend, NodeJS, AI Agents, LLM, MCP, Model Context Protocol, Node.js, TypeScript"},{"@type":"BreadcrumbList","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","position":1,"name":"Home","item":"https:\/\/devdojo.co.in\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","name":"Backend"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","position":2,"name":"Backend","item":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/#listItem","name":"NodeJS"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/#listItem","position":3,"name":"NodeJS","item":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#listItem","name":"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","name":"Backend"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#listItem","position":4,"name":"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)","previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/#listItem","name":"NodeJS"}}]},{"@type":"Organization","@id":"https:\/\/devdojo.co.in\/#organization","name":"DevDojo","description":"Empowering Developers","url":"https:\/\/devdojo.co.in\/"},{"@type":"Person","@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author","url":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/","name":"Lokendra","image":{"@type":"ImageObject","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g","width":96,"height":96,"caption":"Lokendra"}},{"@type":"WebPage","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#webpage","url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/","name":"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo","description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/devdojo.co.in\/#website"},"breadcrumb":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#breadcrumblist"},"author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"creator":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/build-mcp-server-typescript.jpg","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#mainImage","width":1200,"height":630,"caption":"Build an MCP Server in TypeScript \u2013 diagram of an AI app connecting to an MCP server with database, GitHub API and files"},"primaryImageOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/#mainImage"},"datePublished":"2026-10-03T18:31:09+00:00","dateModified":"2026-10-03T18:31:10+00:00"},{"@type":"WebSite","@id":"https:\/\/devdojo.co.in\/#website","url":"https:\/\/devdojo.co.in\/","name":"DevDojo","description":"Empowering Developers","inLanguage":"en-US","publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"}}]},"og:locale":"en_US","og:site_name":"DevDojo - Empowering Developers","og:type":"article","og:title":"How to Build an MCP Server in TypeScript (2026 Guide)","og:description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.","og:url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/","article:published_time":"2026-10-03T18:31:09+00:00","article:modified_time":"2026-10-03T18:31:10+00:00","twitter:card":"summary_large_image","twitter:title":"How to Build an MCP Server in TypeScript (2026 Guide)","twitter:description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP."},"aioseo_meta_data":{"post_id":"164","title":"How to Build an MCP Server in TypeScript (2026 Guide) | DevDojo","description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.","keywords":null,"keyphrases":{"focus":{"keyphrase":"build MCP server TypeScript","score":72},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":"How to Build an MCP Server in TypeScript (2026 Guide)","og_description":"Learn how to build an MCP server in TypeScript with the official SDK v2. 6 hands-on examples: tools, resources, prompts, GitHub API, testing and HTTP.","og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":0,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-10-03 18:25:09","updated":"2026-10-03 19:25:44","focus_keyword":"build MCP server TypeScript","additional_keywords":null,"truseo_locale":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/backend\/\" title=\"Backend\">Backend<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/\" title=\"NodeJS\">NodeJS<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tHow to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/devdojo.co.in\/"},{"label":"Backend","link":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/"},{"label":"NodeJS","link":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/nodejs\/"},{"label":"How to Build an MCP Server in TypeScript (2026 Step-by-Step Guide)","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/03\/build-mcp-server-typescript\/"}],"_links":{"self":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/164","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/comments?post=164"}],"version-history":[{"count":1,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/164\/revisions"}],"predecessor-version":[{"id":165,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/164\/revisions\/165"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media\/158"}],"wp:attachment":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media?parent=164"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/categories?post=164"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/tags?post=164"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}