DEV Community

1xApi
1xApi

Posted on • Originally published at 1xapi.com

How to Build MCP Servers in Node.js for AI Agents (2026 Guide)

How to Build MCP Servers in Node.js for AI Agents (2026 Guide)

The way AI agents interact with APIs is fundamentally changing. In 2026, the Model Context Protocol (MCP) has become the standard for exposing APIs to AI models and autonomous agents. This guide shows you how to build production-ready MCP servers in Node.js that any AI agent can consume directly.

What is MCP and Why It Matters in 2026

The Model Context Protocol (MCP) is an open standard that enables AI models to discover and interact with external tools and services through a standardized interface. Think of it as a universal adapter that lets AI agents call your API functions without needing custom integrations.

According to the Postman State of the API Report, 82% of organizations have adopted API-first approaches, and 2026 marks the year where APIs are no longer just for developers—they're directly consumable by AI agents.

Why MCP is Essential for API Developers

Traditional REST APIs require developers to write custom integration code for each AI model. MCP changes this by providing:

  • Standardized Tool Definitions: AI agents automatically discover available functions
  • Type-Safe Interfaces: Strong typing through TypeScript type hints and docstrings
  • Universal Compatibility: Any MCP-compatible client can consume your API
  • Reduced Integration Work: One endpoint serves all AI agents

Setting Up Your MCP Server Project

Let's build a complete MCP server for a sample API—in this case, a weather API that AI agents can query.

Prerequisites

  • Node.js 20+ (or Bun/Deno)
  • npm or yarn
  • Basic TypeScript knowledge

Initialize the Project

mkdir weather-mcp-server && cd weather-mcp-server
npm init -y
npm install @modelcontextprotocol/server @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Create the MCP Server

Create a file called src/server.ts:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';

// Define tool input schemas using Zod
const GetWeatherInput = z.object({
  location: z.string().describe('City name or coordinates'),
  units: z.enum(['celsius', 'fahrenheit']).optional().default('celsius'),
});

const GetForecastInput = z.object({
  location: z.string(),
  days: z.number().min(1).max(7).optional().default(3),
});

// Mock weather data (replace with real API calls)
const weatherData: Record<string, any> = {
  'new york': { temp: 18, condition: 'Partly Cloudy', humidity: 65 },
  'london': { temp: 12, condition: 'Rainy', humidity: 82 },
  'tokyo': { temp: 22, condition: 'Sunny', humidity: 55 },
  'saigon': { temp: 32, condition: 'Hot', humidity: 78 },
};

class WeatherMCPServer {
  private server: Server;

  constructor() {
    this.server = new Server(
      {
        name: 'weather-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupHandlers();
  }

  private setupHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'get_current_weather',
            description: 'Get current weather for a specific location',
            inputSchema: {
              type: 'object',
              properties: {
                location: {
                  type: 'string',
                  description: 'City name (e.g., "New York", "London", "Tokyo")',
                },
                units: {
                  type: 'string',
                  enum: ['celsius', 'fahrenheit'],
                  description: 'Temperature units',
                  default: 'celsius',
                },
              },
              required: ['location'],
            },
          },
          {
            name: 'get_forecast',
            description: 'Get weather forecast for multiple days',
            inputSchema: {
              type: 'object',
              properties: {
                location: {
                  type: 'string',
                  description: 'City name',
                },
                days: {
                  type: 'number',
                  description: 'Number of days (1-7)',
                  default: 3,
                },
              },
              required: ['location'],
            },
          },
        ],
      };
    });

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        switch (name) {
          case 'get_current_weather':
            return await this.getCurrentWeather(args);
          case 'get_forecast':
            return await this.getForecast(args);
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
            },
          ],
          isError: true,
        };
      }
    });
  }

  private async getCurrentWeather(args: unknown) {
    const input = GetWeatherInput.parse(args);
    const locationLower = input.location.toLowerCase();

    const weather = weatherData[locationLower];

    if (!weather) {
      return {
        content: [
          {
            type: 'text',
            text: `Weather data not found for "${input.location}". Try: New York, London, Tokyo, or Saigon.`,
          },
        ],
      };
    }

    const temp = input.units === 'fahrenheit' 
      ? (weather.temp * 9/5) + 32 
      : weather.temp;
    const unitSymbol = input.units === 'fahrenheit' ? '°F' : '°C';

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            location: input.location,
            temperature: `${temp}${unitSymbol}`,
            condition: weather.condition,
            humidity: `${weather.humidity}%`,
          }, null, 2),
        },
      ],
    };
  }

  private async getForecast(args: unknown) {
    const input = GetForecastInput.parse(args);
    const locationLower = input.location.toLowerCase();

    const weather = weatherData[locationLower];

    if (!weather) {
      return {
        content: [
          {
            type: 'text',
            text: `Location "${input.location}" not found.`,
          },
        ],
      };
    }

    // Generate mock forecast
    const forecast = [];
    for (let i = 0; i < input.days; i++) {
      const dayTemp = weather.temp + Math.floor(Math.random() * 6) - 3;
      forecast.push({
        day: i + 1,
        temp: `${dayTemp}°C`,
        condition: weather.condition,
      });
    }

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            location: input.location,
            forecast,
          }, null, 2),
        },
      ],
    };
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Weather MCP Server running on stdio');
  }
}

const server = new WeatherMCPServer();
server.run().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Configure Package.json

{
  "name": "weather-mcp-server",
  "type": "module",
  "scripts": {
    "start": "tsx src/server.ts",
    "build": "tsc"
  },
  "dependencies": {
    "@modelcontextprotocol/server": "^1.0.0",
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Add MCP Configuration

Create mcp.json for clients to discover your server:

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/path/to/weather-mcp-server/dist/server.js"],
      "env": {}
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Testing Your MCP Server

Using the MCP Inspector

npx @modelcontextprotocol/inspector node dist/server.js
Enter fullscreen mode Exit fullscreen mode

This opens a web interface where you can test your tools interactively.

Testing with a Simple Client

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

async function test() {
  const transport = new StdioClientTransport({
    command: 'node',
    args: ['dist/server.js'],
  });

  const client = new Client(
    { name: 'test-client', version: '1.0.0' },
    { capabilities: {} }
  );

  await client.connect(transport);

  // List available tools
  const tools = await client.request(
    { method: 'tools/list' },
    { method: 'tools/list', params: {} }
  );
  console.log('Available tools:', tools);

  // Call a tool
  const result = await client.request(
    { method: 'tools/call' },
    {
      method: 'tools/call',
      params: {
        name: 'get_current_weather',
        arguments: { location: 'Tokyo', units: 'celsius' },
      },
    }
  );
  console.log('Weather result:', result);
}

test();
Enter fullscreen mode Exit fullscreen mode

Exposing Your MCP Server Over HTTP

While MCP traditionally uses stdio, you can expose it over HTTP for remote AI agents:

import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { HttpServerTransport } from '@modelcontextprotocol/sdk/server/http.js';

const app = express();
app.use(express.json());

const mcpServer = new Server({ name: 'api-mcp', version: '1.0.0' }, {
  capabilities: { tools: {} },
});

// ... define tools as shown above

app.post('/mcp', async (req, res) => {
  const transport = new HttpServerTransport(req, res);
  await mcpServer.connect(transport);
});

app.listen(3000, () => {
  console.log('MCP HTTP Server running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Best Practices for Production MCP Servers

1. Use Type-Safe Tool Definitions

Always define input schemas with Zod or TypeScript for compile-time validation:

const CreateUserInput = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
});
Enter fullscreen mode Exit fullscreen mode

2. Implement Proper Error Handling

Return structured errors that AI agents can understand:

try {
  // Your tool logic
} catch (error) {
  return {
    content: [{ type: 'text', text: `Error: ${error.message}` }],
    isError: true,
  };
}
Enter fullscreen mode Exit fullscreen mode

3. Add Comprehensive Descriptions

The description field is critical—AI agents use it to understand when to call your tools:

{
  name: 'create_order',
  description: 'Create a new order. Use this when a customer wants to purchase products. Requires authenticated session.',
  inputSchema: {
    type: 'object',
    properties: {
      items: { 
        type: 'array', 
        description: 'Array of {productId, quantity} objects' 
      },
      shippingAddress: {
        type: 'string',
        description: 'Full shipping address including city and postal code',
      },
    },
    required: ['items', 'shippingAddress'],
  },
}
Enter fullscreen mode Exit fullscreen mode

4. Secure Your MCP Endpoints

Add authentication for production servers:

const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token || !validateToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
};

app.post('/mcp', authMiddleware, async (req, res) => {
  // Handle MCP requests
});
Enter fullscreen mode Exit fullscreen mode

Connecting MCP to 1xAPI

You can expose your 1xAPI endpoints as MCP tools, making them accessible to AI agents:

import fetch from 'node-fetch';

const TranslateTool = {
  name: 'translate_text',
  description: 'Translate text between languages using 1xAPI',
  inputSchema: {
    type: 'object',
    properties: {
      text: { type: 'string', description: 'Text to translate' },
      from: { type: 'string', description: 'Source language code (e.g., en, my)' },
      to: { type: 'string', description: 'Target language code' },
    },
    required: ['text', 'from', 'to'],
  },
};

async function translate(args: any) {
  const response = await fetch('https://api.1xapi.com/translate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.1XAPI_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(args),
  });

  const result = await response.json();
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The Model Context Protocol is transforming how AI agents interact with APIs. By exposing your 1xAPI as MCP servers, you enable AI agents to consume your services natively—without custom integration code for each agent.

Key takeaways:

  • MCP provides a standardized way for AI agents to discover and call your API functions
  • Use Zod for type-safe input validation
  • Add comprehensive descriptions to help AI agents understand when to use your tools
  • Implement proper authentication for production deployments
  • Consider HTTP transport for remote AI agent access

As AI agents become more prevalent, MCP will likely become as essential as REST APIs were for web services. Start building your MCP servers today to stay ahead of this shift.


Related Links:

Top comments (0)