DEV Community

Nicolas Dabene
Nicolas Dabene

Posted on • Originally published at nicolas-dabene.fr

Create Your First MCP Server: Setup

Building Your First MCP Server: A TypeScript Project Kickoff

If you've already grasped the core concepts of the Model Context Protocol (MCP), then you're perfectly primed for the next step: bringing that theory to life! In this guide, we'll establish the robust foundation for a functional MCP server. Think of it like laying the groundwork for a sturdy building – we'll start with the essentials, setting up everything step by step.

Getting Started: Laying the Foundation

Throughout my journey as a developer, there’s nothing quite as satisfying as seeing abstract ideas materialize into working code. Remember the thrill of your very first "Hello World"? We're about to experience that same excitement, but this time, with an MCP twist. This article will walk you through preparing your development environment using Node.js, TypeScript, and Express.js. Don't fret; these are just the right tools for the job, and we'll integrate them seamlessly.

Our objective is straightforward: by the end of this tutorial, you'll have a meticulously configured project, fully equipped to host your MCP server's logic. Envision it as setting up your ideal workspace before diving into a complex DIY project – organizing your tools, ensuring everything is on hand, and then creating with confidence.

Why This Tech Stack?

Before we jump straight into terminal commands, let’s pause to understand the rationale behind our technology choices. While you’re not strictly bound to this specific stack, I highly recommend it, especially if you’re new to this kind of development.

Node.js: The JavaScript Runtime

Node.js empowers you to execute JavaScript on the server side. It has evolved into an industry benchmark, boasting a vast, active community and an abundance of available packages. For our MCP server, Node.js grants us access to critical functionalities like file system interactions, network management, and everything essential for creating a bridge between AI and your data.

TypeScript: Enhanced Type Safety

Think of TypeScript as JavaScript with an added layer of assurance. It introduces static typing, which effectively catches numerous common coding errors before they even become runtime problems. When developing a server that will process AI requests and potentially handle sensitive information, this level of compile-time verification is incredibly reassuring. Plus, the developer experience in your code editor improves dramatically with TypeScript's intelligent auto-completion.

Express.js: The Lean Web Framework

Express.js serves as the ultimate utility knife for Node.js web development. It enables us to effortlessly define API routes, manage incoming HTTP requests, and structure our server in a clean, organized manner. It’s remarkably lightweight, fast, and an ideal fit for our MCP server, which will be designed to process structured JSON commands.

Essential Prerequisites

Before we embark on our coding journey, please ensure the following tools are installed on your machine:

  • Node.js (version 16 or newer): Confirm your installation with node --version in your terminal. If you don't have it, download the latest LTS version from the official nodejs.org website.
  • npm (Node Package Manager): This typically comes bundled with Node.js. Verify its presence using npm --version.
  • A Reliable Code Editor: Visual Studio Code is an excellent choice for TypeScript development, but feel free to use whichever editor you are most comfortable with.
  • A Command-Line Interface: Whether it's Bash, Zsh, PowerShell, or something else, as long as you can execute commands, you're good to go!

All set? Fantastic! Let’s open up your terminal.

Step 1: Project Folder Creation and Initialization

Our first task is to create our project workspace. Navigate to your desired directory in the terminal and execute these commands:

mkdir mcp-server
cd mcp-server
npm init -y
Enter fullscreen mode Exit fullscreen mode

What just occurred?

The initial line establishes a new directory called mcp-server, which will house our entire project. The second command moves your terminal's active location into this newly created folder. Finally, npm init -y initializes a new Node.js project.

The -y flag serves as a shortcut, automatically accepting all default configuration options. Without it, npm would prompt you with a series of questions regarding your project's name, version, description, and so on. For our current needs, the default values are perfectly adequate.

This action creates a vital file: package.json. This file acts as your project's manifest, listing all its dependencies, available scripts, and other essential metadata. If you open package.json now, you’ll observe content similar to this:

{
  "name": "mcp-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

It’s a basic starting point, but it will evolve significantly as we add more components.

Step 2: Integrating TypeScript

Next, we’ll equip our project with TypeScript. Enter the following command into your terminal:

npm install typescript ts-node @types/node --save-dev
Enter fullscreen mode Exit fullscreen mode

Let's deconstruct this command:

The npm install part is quite self-explanatory: we're installing software packages. The --save-dev flag is crucial here; it signifies that these packages are development dependencies, meaning they are only required during the development phase and not in the final production environment.

Here’s a brief overview of each package's role:

  • typescript: This is the TypeScript compiler itself. Its primary function is to transform your TypeScript code into standard JavaScript, which Node.js can then execute.
  • ts-node: A powerful utility that allows you to run TypeScript code directly without the need for manual compilation steps. This convenience significantly speeds up the development workflow.
  • @types/node: These are type definitions specifically for the Node.js environment. Thanks to this package, TypeScript can understand and provide type-checking for native Node.js functions like fs.readFile, path.join, and many others.

After the installation completes, inspect your package.json file once more. You’ll find a new section has appeared:

"devDependencies": {
  "@types/node": "^20.10.0",
  "ts-node": "^10.9.2",
  "typescript": "^5.3.3"
}
Enter fullscreen mode Exit fullscreen mode

The specific version numbers might differ based on when you run the command, but the fundamental structure and purpose remain identical.

Step 3: Configuring TypeScript

TypeScript relies on a configuration file to understand how to compile your source code. Let's generate this file:

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

A quick technical note: The npx command allows you to execute an npm package executable without explicitly installing it globally. In this instance, it's launching the TypeScript compiler (tsc) with the --init option, which creates the default configuration file.

This command will generate a tsconfig.json file. You’ll notice it’s heavily commented, with detailed explanations for each option, which can be quite educational! For our current project, here are the most important settings we'll be working with:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

Brief explanations of these key options:

  • target: Specifies the JavaScript version that the TypeScript compiler will output. ES2020 is a modern and widely supported choice.
  • module: Defines the module system to be used. commonjs is the standard for Node.js environments.
  • outDir: Determines the destination folder for your compiled JavaScript files (we'll create this directory shortly).
  • rootDir: Indicates where your TypeScript source code files are located.
  • strict: Activates all of TypeScript's strict type-checking options. While it can be demanding, it’s invaluable for preventing subtle bugs.
  • esModuleInterop: Enables better compatibility when importing CommonJS modules into ES modules and vice-versa.
  • skipLibCheck: Skips type checking of declaration files (.d.ts).
  • forceConsistentCasingInFileNames: Ensures consistent casing in file paths.

For now, the default values generated are perfectly suitable. You can always fine-tune these settings later as your project's requirements evolve.

Step 4: Integrating Express.js

The final building block for our server's foundation is Express.js, the framework that will gracefully manage our HTTP requests. Install it with these commands:

npm install express
npm install @types/express --save-dev
Enter fullscreen mode Exit fullscreen mode

Why two separate commands?

  • express: This is the core Express.js library itself, which is required for your application to run in production. Therefore, we omit the --save-dev flag.
  • @types/express: These are the TypeScript type definitions specifically for Express.js. They are only necessary during development to provide type-checking and auto-completion benefits, hence the --save-dev flag.

Your updated package.json file should now resemble this structure:

{
  "name": "mcp-server",
  "version": "1.0.0",
  "description": "MCP server for AI connection to local data",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "mcp",
    "ai",
    "server"
  ],
  "author": "Your Name",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.10.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

Again, minor version discrepancies are normal, but the crucial dependencies should be in place.

Verifying the Installation

Before moving forward, let’s quickly confirm that everything is working as expected. Begin by creating a src folder and a simple test file within it:

mkdir src
Enter fullscreen mode Exit fullscreen mode

Now, create a file named src/index.ts and add the following basic content:

import express from 'express';

const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.json({ message: 'MCP server operational!' });
});

app.listen(PORT, () => {
  console.log(`✅ Server launched on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

What does this code snippet do?

This code imports the Express library and initializes an application instance. It then defines a simple GET route for the root path (/) that responds with a JSON object containing a success message. Finally, it starts the server, listening for incoming requests on port 3000.

To easily execute this code, let's enhance our package.json by adding a new script. Modify the scripts section as shown:

"scripts": {
  "dev": "ts-node src/index.ts",
  "test": "echo \"Error: no test specified\" && exit 1"
}
Enter fullscreen mode Exit fullscreen mode

Now, from your terminal, run the following command:

npm run dev
Enter fullscreen mode Exit fullscreen mode

If your environment is configured correctly, you should observe this output in your terminal:

✅ Server launched on http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Open your web browser and navigate to http://localhost:3000. You should be greeted with a JSON response:

{
  "message": "MCP server operational!"
}
Enter fullscreen mode Exit fullscreen mode

Congratulations! Your development environment is now perfectly set up and ready for action.

Project Organization

Before we conclude, let’s establish a logical structure for our project files. Here’s the recommended directory layout:

mcp-server/
├── src/
│   ├── index.ts          # The primary server entry point
│   ├── routes/           # (To be created later) Express route definitions
│   ├── tools/            # (To be created later) Custom MCP tools (e.g., readFile)
│   └── types/            # (To be created later) Custom TypeScript type declarations
├── dist/                 # Compiled JavaScript output (generated automatically)
├── node_modules/         # Installed third-party dependencies (typically ignored by Git)
├── package.json
├── tsconfig.json
└── .gitignore
Enter fullscreen mode Exit fullscreen mode

Additionally, create a .gitignore file at the root of your project to prevent unnecessary files from being tracked by Git:

node_modules/
dist/
*.log
.env
.DS_Store
Enter fullscreen mode Exit fullscreen mode

What’s Next?

We’ve successfully laid a robust foundation! Here’s a sneak peek at what’s coming in the subsequent articles of this series:

  • Part 3: We'll dive into creating our very first MCP tool, the readFile function, which will empower AI to access and read local files.
  • Part 4: You'll learn how to implement the tool discovery system, often referred to as the "menu," allowing AI to understand available functionalities.
  • Part 5: We'll focus on crucial aspects of security, including managing permissions, input validation, and overall server protection.
  • Part 6: The exciting finale – testing our fully functional server with popular AI models like Claude or ChatGPT.

Conclusion

You’ve just completed the essential groundwork for your MCP server. While it may not be performing complex tasks just yet, all the necessary components are perfectly aligned. It’s akin to having a meticulously organized kitchen before preparing a gourmet meal: everything is within reach, allowing you to concentrate fully on the culinary art itself.

In our next article, we’ll move beyond setup and implement our first practical MCP tool: the readFile function. This is where the true power of connecting AI to your local machine really starts to unfold!

Feel free to explore and experiment with this setup. Try adding more Express routes, tinker with TypeScript, and generally familiarize yourself with the environment. The more comfortable you become with these fundamentals, the smoother your future development experience will be.


Dive Deeper!

Also read the other articles in this series:


Enjoyed this guide and want more practical insights into AI and development?
Subscribe to my YouTube channel for video tutorials and deep dives: Nicolas Dabène on YouTube

Let's connect and grow together!
Follow me on LinkedIn for more tech discussions and updates: Nicolas Dabène on LinkedIn

Top comments (0)