Create Your First MCP Server: TypeScript Project Setup
You understood the Model Context Protocol theory in our previous article? Perfect! It’s time to get hands-on with code. We’ll build together the foundations of a functional MCP server. Don’t panic, we’ll go step by step, as if we were building a house: first the foundations, then the walls, then the roof.
Introduction
In my developer career, I’ve always been fascinated by that magical moment when theory becomes reality. Remember your first “Hello World”? That’s exactly what we’ll experience together, but MCP version. In this article, we’ll prepare our development environment with Node.js, TypeScript and Express.js. Nothing complicated, just the right tools in the right place.
The goal is simple: at the end of this tutorial, you’ll have a perfectly configured project, ready to host your MCP server logic. Think of this as preparing your workshop before starting a DIY project: we arrange tools, check we have everything we need, and then we can create serenely.
Why These Technologies?
Before rushing headlong into commands, let’s take a minute to understand our technological choices. You’re not obliged to use exactly this stack, but here’s why I recommend it for beginners.
Node.js: The Runtime Environment
Node.js allows running JavaScript server-side. It’s become an industry standard, with a massive community and thousands of available packages. For our MCP server, Node.js gives us access to the file system, network management and everything we need to create a bridge between AI and your data.
TypeScript: Type Safety
TypeScript is like JavaScript with a safety net. It adds static types that save us from many stupid errors. When you build a server that will handle AI requests and potentially manipulate sensitive data, having this level of verification is reassuring. Moreover, auto-completion in your editor becomes magical with TypeScript.
Express.js: The Minimalist Web Framework
Express is the Swiss Army knife of Node.js web development. It allows us to create routes, handle HTTP requests and structure our server properly. It’s lightweight, fast and perfectly suited for our MCP server which will receive JSON commands.
Prerequisites Before Starting
Make sure you have installed on your machine:
- Node.js version 16 or higher: Check with
node --versionin your terminal. If you don’t have Node.js, download it from the official nodejs.org website - npm (Node Package Manager): It comes automatically with Node.js. Check with
npm --version - A code editor: Visual Studio Code is excellent for TypeScript, but use the one you’re comfortable with
- A terminal: Bash, Zsh, PowerShell… doesn’t matter, as long as you can launch commands
Everything ready? Perfect, let’s open our terminal!
Step 1: Folder Creation and Initialization
Let’s start by creating our workspace. Open your terminal and type these commands:
<span class="nb">mkdir </span>mcp-server
<span class="nb">cd </span>mcp-server
npm init <span class="nt">-y</span>
What just happened?
The first line creates a new folder named mcp-server. It’s our house, our project. The second line makes us enter it. The third line initializes a Node.js project with npm init -y.
The -y flag means “yes to all”: it automatically accepts all default options. Without this flag, npm would ask you a series of questions (project name, version, description…). For now, default values are perfect.
This command creates a crucial file: package.json. It’s your project’s identity card. It lists all dependencies, available scripts and project metadata. If you open this file, you’ll see something like this:
<span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mcp-server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"echo </span><span class="se">\"</span><span class="s2">Error: no test specified</span><span class="se">\"</span><span class="s2"> && exit 1"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"keywords"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ISC"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
It’s basic, but it will evolve as we go.
Step 2: Installing TypeScript
Now, let’s equip our project with TypeScript. Type this command:
npm <span class="nb">install </span>typescript ts-node @types/node <span class="nt">--save-dev</span>
Let’s break down this command:
The npm install is fairly explicit: we’re installing packages. The --save-dev indicates that these packages are development dependencies, needed only during development, not in production.
Here’s what each package does:
- typescript: The TypeScript compiler itself. It transforms your TypeScript code into JavaScript that Node.js can run
- ts-node: A magical tool that allows directly running TypeScript code without having to compile manually. During development, this saves tons of time
- @types/node: Type definitions for the Node.js environment. Thanks to this, TypeScript understands native Node functions like
fs.readFile,path.join, etc.
After installation, look at your package.json. A new section has appeared:
<span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@types/node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^20.10.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"ts-node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^10.9.2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"typescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^5.3.3"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Versions may vary depending on when you read this article, but the principle remains the same.
Step 3: Configuring TypeScript
TypeScript needs a configuration file to know how to compile your code. Let’s generate it:
npx tsc <span class="nt">--init</span>
Small technical point: npx runs a package without installing it globally. Here, it launches the TypeScript compiler (tsc) with the --init option which creates the configuration file.
This command generates a tsconfig.json file with lots of comments explaining each option. It’s verbose, but instructive! Here are the most important options for our project:
<span class="p">{</span><span class="w">
</span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ES2020"</span><span class="p">,</span><span class="w">
</span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"commonjs"</span><span class="p">,</span><span class="w">
</span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./dist"</span><span class="p">,</span><span class="w">
</span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./src"</span><span class="p">,</span><span class="w">
</span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"esModuleInterop"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"skipLibCheck"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"forceConsistentCasingInFileNames"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"include"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"src/**/*"</span><span class="p">],</span><span class="w">
</span><span class="nl">"exclude"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"node_modules"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Some quick explanations:
- target: The target JavaScript version. ES2020 is modern and well supported
- module: The module system. CommonJS is the Node.js standard
- outDir: Where to place compiled JavaScript files (we’ll create this folder later)
- rootDir: Where our TypeScript source code is located
- strict: Activates all TypeScript strict checks. It’s a bit picky, but it avoids bugs
For now, the generated default values are perfect. You can refine later according to your needs.
Step 4: Installing Express.js
Last brick of our foundation: Express.js, the framework that will handle our HTTP requests. Install it with:
npm <span class="nb">install </span>express
npm <span class="nb">install</span> @types/express <span class="nt">--save-dev</span>
Why two commands?
- express: The library itself, needed in production. So we don’t put
--save-dev - @types/express: Type definitions for Express, needed only during development. Hence the
--save-dev
Your final package.json should now look like this:
<span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mcp-server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MCP server for AI connection to local data"</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"echo </span><span class="se">\"</span><span class="s2">Error: no test specified</span><span class="se">\"</span><span class="s2"> && exit 1"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"keywords"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"mcp"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ai"</span><span class="p">,</span><span class="w"> </span><span class="s2">"server"</span><span class="p">],</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Your Name"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ISC"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"express"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^4.18.2"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@types/express"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^4.17.21"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@types/node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^20.10.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"ts-node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^10.9.2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"typescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^5.3.3"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Version numbers may vary slightly, but the essentials are there.
Installation Verification
Before going further, let’s verify everything works. Create a src folder and a test file:
<span class="nb">mkdir </span>src
Create a src/index.ts file with this simple content:
<span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">MCP server operational!</span><span class="dl">'</span> <span class="p">});</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`✅ Server launched on http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">});</span>
What does this code do?
We import Express and create an application. We define a GET route on / that returns a simple JSON message. We launch the server on port 3000.
To run this code, let’s add a script in our package.json. Modify the scripts section:
<span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ts-node src/index.ts"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"echo </span><span class="se">\"</span><span class="s2">Error: no test specified</span><span class="se">\"</span><span class="s2"> && exit 1"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Now, launch:
npm run dev
If everything is well configured, you should see in your terminal:
✅ Server launched on http://localhost:3000
Open your browser and go to http://localhost:3000. You should see:
<span class="p">{</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MCP server operational!"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
Congratulations! Your environment is perfectly configured.
Project Organization
Before finishing, let’s organize our project a bit. Here’s the structure I recommend:
mcp-server/
├── src/
│ ├── index.ts # Server entry point
│ ├── routes/ # Express routes (to create later)
│ ├── tools/ # MCP tools (readFile, etc.)
│ └── types/ # Custom TypeScript definitions
├── dist/ # Compiled code (automatically generated)
├── node_modules/ # Dependencies (ignored by Git)
├── package.json
├── tsconfig.json
└── .gitignore
Also create a .gitignore file to avoid versioning useless files:
node_modules/
dist/
*.log
.env
.DS_Store
Next Steps
We now have a solid foundation! Here’s what awaits us in the next articles of this series:
- Part 3: Create our first MCP
readFiletool that will allow AI to read local files - Part 4: Implement the tool discovery system (the famous “menu”)
- Part 5: Manage permissions and security
- Part 6: Test our server with Claude or ChatGPT
Conclusion
We’ve just laid the solid foundations of our MCP server. Sure, it doesn’t do much for now, but all the tools are in place. It’s like having prepared your kitchen before starting to cook a complex meal: everything is arranged, accessible, and we can focus on the essentials.
In the next article, we’ll create our first real MCP tool: the readFile function that will allow an AI to read files on your machine. That’s where the magic really begins!
Don’t hesitate to experiment with this setup. Try adding other Express routes, play with TypeScript, familiarize yourself with the environment. The more comfortable you are with these basics, the more natural the rest will be.
Also read:
- Understanding the Model Context Protocol (MCP): A Simple Conversation
- Create Your First MCP Tool: The readFile Tool Explained
- The MCP Menu: How AI Discovers and Uses Your Tools
- Securing Your MCP Server: Permissions, Validation and Protection
- Connect Your MCP Server to Claude Desktop: Complete Integration