Introduction

Welcome to your first MCP (Model Context Protocol) server! This tutorial will walk you through creating a simple “Hello World” MCP server from scratch. Don’t worry if you’ve never built an MCP server before - we’ll explain everything step by step.

What You’ll Learn

By the end of this tutorial, you’ll have:
  • Created your first working MCP server using HTTP transport
  • Connected it to an AI assistant through a remote connection
  • Understood the basic structure of MCP servers
  • Learned how to add simple tools and deploy them

Prerequisites

Before we start, make sure you have:
  • Node.js (version 18 or higher) installed on your computer
  • npm or pnpm package manager
  • A text editor (like VS Code, Sublime Text, or any editor you prefer)
  • Basic familiarity with JavaScript/TypeScript (don’t worry, we’ll keep it simple!)
New to Node.js? Download it from nodejs.org. The installer will also include npm.

Step 1: Set Up Your Project

First, let’s create a new folder for our MCP server and set up the basic project structure.
  1. Open your terminal or command prompt
  2. Create a new directory and navigate to it:
mkdir my-first-mcp-server
cd my-first-mcp-server
  1. Initialize a new Node.js project:
npm init -y
This creates a package.json file with default settings. You should see something like this in your folder now.

Step 2: Install MCP Dependencies

Now we need to install the MCP SDK and Express for our HTTP server.
npm install @modelcontextprotocol/sdk express
We’re using Express to create an HTTP server, which makes our MCP server accessible over the network. This is more flexible than stdio transport and allows for easier deployment and testing.

Step 3: Create Your First MCP Server

Now for the exciting part - let’s create our actual MCP server! Create a new file called server.js in your project folder:
// server.js
import express from "express";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

/* ---------- Build a tiny MCP server ----------------------------------- */
function buildHelloWorldServer() {
  const mcp = new Server(
    { name: "hello-world-server", version: "1.0.0" },
    { capabilities: { tools: {} } },
  );

  // Tell clients what tools we expose
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools: [
      {
        name: "say_hello",
        description: "Says hello to someone",
        inputSchema: {
          type: "object",
          properties: {
            name: { type: "string", description: "Who to greet" },
            message: {
              type: "string",
              description: "Optional custom message (defaults to 'Hello')",
            },
          },
          required: ["name"],
        },
      },
    ],
  }));

  // Do the work when the tool is called
  mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
    const { name, arguments: args } = req.params;
    if (name !== "say_hello") throw new Error(`Unknown tool: ${name}`);

    const greeting = args.message || "Hello";
    return {
      content: [
        {
          type: "text",
          text: `${greeting}, ${args.name}! 👋 Served over Streamable HTTP.`,
        },
      ],
    };
  });

  return mcp;
}

/* ---------- Express wrapper ------------------------------------------- */
const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;
const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
  const mcpServer = buildHelloWorldServer();

  // Stateless transport: JSON response, no sticky session
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,     
  });

  res.on("close", () => {
    transport.close();
    mcpServer.close?.(); 
  });

  await mcpServer.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.get("/mcp", (req, res) =>
  res.status(405).json({ error: "GET not allowed in stateless mode" }),
);

app.listen(PORT, () => {
  console.log(`✅ MCP server listening at http://localhost:${PORT}/mcp`);
});
Make sure to save this file as server.js in your project folder. The code might look complex, but don’t worry - we’ll break it down!

Step 4: Understanding Your Server Code

Let’s break down what each part of your server does:

The MCP Server Setup

const mcp = new Server(
  { name: "hello-world-server", version: "1.0.0" },
  { capabilities: { tools: {} } },
);
This creates your MCP server with a name and tells it that it can provide “tools” to AI assistants.

Tool Definition

The ListToolsRequestSchema handler tells AI assistants what your server can do. Our server has one tool called say_hello that:
  • Takes a person’s name (required)
  • Takes an optional custom message
  • Returns a greeting

Tool Execution

The CallToolRequestSchema handler is what actually runs when an AI assistant uses your tool. It takes the person’s name and message, then returns a friendly greeting.

HTTP Transport

The Express wrapper creates an HTTP endpoint that:
  • Listens on port 3000 (configurable via PORT environment variable)
  • Accepts POST requests at /mcp
  • Creates a new MCP server instance for each request (stateless)
  • Uses StreamableHTTPServerTransport for communication

Step 5: Update Your Package.json

We need to tell Node.js that our project uses ES modules. Open your package.json file and add this line:
{
  "name": "my-first-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.5.0",
    "express": "^4.18.0"
  }
}
The important addition is "type": "module" which allows us to use modern JavaScript import syntax.

Step 6: Test Your Server

Let’s make sure your server works! In your terminal, run:
PORT=3000 node server.js
You should see:
✅ MCP server listening at http://localhost:3000/mcp
Great! Your server is running. Keep this terminal window open.
If you see any errors, double-check that you’ve copied the code exactly and that your package.json includes "type": "module".

Step 7: Connect to Pylee

Now let’s connect your server to Pylee so you can use it with AI assistants!
  1. Make sure your server is running:
    PORT=3000 node server.js
    
  2. Open Pylee in your web browser and go to the Server Setup page
  3. Configure your server:
    • Server Name: My First Hello World Server
    • Connection Type: Choose “Remote Connection”
    • URL: http://localhost:3000/mcp
Using HTTP transport means your server can be accessed from anywhere. While we’re using localhost for testing, you can later deploy this to any cloud provider!
  1. Test the connection using Pylee’s test feature
  2. Save your configuration

Step 7.5: Adding Environment Variables and Secrets (Optional)

This server doesn’t require any environment variables or secrets to be configrued. You can skip this. but as your MCP server grows, you’ll likely need to add API keys or other sensitive configuration. Pylee provides secure ways to manage these:

Environment Variables in Pylee

When configuring your server in Pylee, you can add environment variables that will be passed to your server:
  1. In the server configuration, look for the Environment Variables section
  2. Add variables like:
    API_KEY=your-api-key-here
    DATABASE_URL=postgresql://...
    
  3. Mark sensitive values as Secret to hide them from view
⚠️ Security Best Practice: Never put sensitive information directly in your server configuration if it’s public. Instead:
  • Use environment variables at the Organization or Registry level for secrets
  • Only put non-sensitive configuration at the server level

Using Environment Variables in Your Code

Update your server to use environment variables:
// Example: Using an API key from environment
const API_KEY = process.env.API_KEY;
const DATABASE_URL = process.env.DATABASE_URL;

// In your tool handler:
if (!API_KEY) {
  throw new Error("API_KEY environment variable is required");
}

Step 8: Use Your Server with AI

Once connected, you can now use your MCP server with any AI assistant connected to Pylee! Try asking:
“Use the say_hello tool to greet John with a custom message ‘Good morning’”
The AI will use your MCP server and you should get a response like:
“Good morning, John! 👋 Served over Streamable HTTP.”

What’s Next?

Congratulations! You’ve successfully created and deployed your first MCP server using HTTP transport. Here are some ideas to expand your server:

Deploy to Production

Since you’re using HTTP transport, deployment is straightforward! Popular options include:
  • Vercel - Great for serverless deployments with automatic HTTPS
  • Render - Simple deployment with free tier
  • Traditional cloud providers - AWS, Google Cloud, Azure
Coming Soon: Pylee will soon offer native MCP server hosting, making deployment even easier without managing your own infrastructure!

Development vs Production Servers

When moving to production, consider setting up separate environments:
  1. Development Server:
    URL: http://localhost:3000/mcp
    Environment: Local development
    
  2. Production Server:
    URL: https://your-server.vercel.app/mcp
    Environment: Production with proper secrets
    
You can register both in Pylee with different names:
  • hello-world-dev (for testing)
  • hello-world-prod (for production use)

Production-Ready Code Example

Here’s how to modify your server for production:
// server.js - production ready version
const PORT = process.env.PORT || 3000;
const NODE_ENV = process.env.NODE_ENV || 'development';

// ... rest of your server code ...

app.listen(PORT, () => {
  if (NODE_ENV === 'production') {
    console.log(`✅ MCP server running in production mode`);
  } else {
    console.log(`✅ MCP server listening at http://localhost:${PORT}/mcp`);
  }
});

Using Registries for Organization

Pylee’s Registries feature lets you organize servers with shared configuration:
  1. Create a Development Registry:
    • Add your dev servers
    • Configure test API keys
    • Share with your development team
  2. Create a Production Registry:
    • Add production servers
    • Configure production secrets at the registry level
    • Control access for production use
Registries are perfect for teams! You can configure secrets once at the registry level and share them across multiple servers.

Add More Tools

Try adding a new tool to your server that uses environment variables. For example, a weather tool:
{
  name: "get_weather",
  description: "Gets weather for a city",
  inputSchema: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" }
    },
    required: ["city"],
  },
}

// In your handler:
if (name === "get_weather") {
  const WEATHER_API_KEY = process.env.WEATHER_API_KEY;
  if (!WEATHER_API_KEY) {
    return {
      content: [{
        type: "text",
        text: "Weather API key not configured. Please set WEATHER_API_KEY in your environment."
      }]
    };
  }
  // Make API call with the key...
}

Add Resources

MCP servers can also provide resources - think of them as files or data that AI assistants can read.

Learn Advanced Features

Troubleshooting

Common Issues

“Module not found” errors
  • Make sure you’ve run npm install
  • Check that your package.json includes "type": "module"
“Server won’t start” errors
  • Verify your Node.js version is 18 or higher: node --version
  • Check for syntax errors in your server.js file
  • Make sure port 3000 is not already in use
“Connection failed” in Pylee
  • Make sure your server is running (PORT=3000 node server.js)
  • Check that the URL is exactly http://localhost:3000/mcp
  • Try testing with curl to verify the server is responding

Testing with Curl

You can test your server independently with:
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1
  }'

Getting Help

If you’re stuck, here are some resources:

Summary

You’ve just built your first MCP server using HTTP transport! Here’s what you accomplished: ✅ Created a new MCP server with HTTP transport
✅ Added a custom tool that AI assistants can use
✅ Connected it remotely to Pylee
✅ Learned how to manage environment variables and secrets
✅ Understood deployment options and dev/prod separation
✅ Tested it with an AI assistant

Why HTTP Transport?

By using HTTP transport instead of stdio, you’ve gained:
  • Remote accessibility - Your server can run anywhere
  • Better debugging - Test with tools like curl or Postman
  • Production readiness - Deploy to any cloud provider
  • Scalability - Handle multiple concurrent requests
  • Environment flexibility - Easy dev/prod separation

Next Steps for Production

  1. Deploy your server to Vercel, Railway, or another provider
  2. Set up separate dev/prod configurations in Pylee
  3. Use registries to organize servers and share with your team
  4. Configure secrets properly at the organization or registry level
  5. Wait for Pylee hosting - Native MCP server hosting coming soon!
Your server might be simple, but it demonstrates all the core concepts of MCP servers. Every complex MCP server starts with these same basic building blocks. The most important thing to remember is that MCP servers are just programs that follow a specific protocol to communicate with AI assistants. Once you understand this basic pattern, you can build servers that connect to databases, APIs, file systems, or any other system you can imagine! Ready to build something more complex? Check out our other MCP server guides to learn advanced techniques and best practices.