BOVO Digital
BOVO Digital
Tutorials14 min read

How to Build an MCP Server in TypeScript in 30 Minutes

The Model Context Protocol (MCP) is the standard that lets AI agents access your proprietary data. This tutorial guides you step by step: installation, first tool, connection to Cursor. Ready in 30 minutes.

William Aklamavo
William Aklamavo

How to Build an MCP Server in TypeScript in 30 Minutes

How to Build an MCP Server in TypeScript in 30 Minutes

The Model Context Protocol (MCP) is the emerging standard that allows AI agents (Claude, Cursor, GPT-5) to access your proprietary data and systems in a secure, structured way. If you've ever wanted your AI agent to consult your CRM, your database or your internal documentation — that's exactly what MCP makes possible.

This tutorial guides you step by step to create your first MCP server in TypeScript, from installation to connection in Cursor.

Why MCP Rather Than a Simple API?

The legitimate question: why not simply connect the AI agent to your existing REST API?

The fundamental difference: MCP is a tool discovery protocol. The AI agent can ask your MCP server "what tools do you have available?" and receives a structured list with descriptions, parameters and types. The agent then understands what to do with these tools without manual configuration on your side.

With a classic REST API, you need to explain to the agent how to use it in every prompt. With MCP, you do it once in the code, and every agent connecting to your server automatically understands.

Prerequisites

  • Node.js 20 or higher
  • TypeScript installed globally: npm install -g typescript
  • A CRM account or database (for the example, we'll use a simulated CRM)

Step 1 — Project Initialization

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk express
npm install -D typescript @types/node @types/express ts-node
npx tsc --init

Configure your tsconfig.json for ESM mode:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}

Step 2 — Create the Basic MCP Server

// src/index.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";

const server = new Server(
  { name: "my-crm-mcp", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Simulated CRM data
const clients = [
  { id: "1", name: "Acme Corp", email: "contact@acme.com", pipeline: "Negotiation", value: 15000 },
  { id: "2", name: "Globex Inc", email: "info@globex.com", pipeline: "Prospect", value: 5000 },
  { id: "3", name: "Initech LLC", email: "hello@initech.io", pipeline: "Active client", value: 42000 },
];

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "get_client",
      description: "Retrieves client information by email or name",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "Client email or name" }
        },
        required: ["query"]
      }
    },
    {
      name: "list_pipeline",
      description: "Lists all clients at a sales pipeline stage",
      inputSchema: {
        type: "object",
        properties: {
          stage: { type: "string", description: "Pipeline stage (Prospect, Negotiation, Active client)" }
        },
        required: ["stage"]
      }
    }
  ]
}));

Step 3 — Implement Tool Handlers

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

  if (name === "get_client") {
    const { query } = args as { query: string };
    const client = clients.find(c =>
      c.email.includes(query) || c.name.toLowerCase().includes(query.toLowerCase())
    );
    if (!client) return { content: [{ type: "text", text: "No client found" }] };
    return { content: [{ type: "text", text: JSON.stringify(client, null, 2) }] };
  }

  if (name === "list_pipeline") {
    const { stage } = args as { stage: string };
    const results = clients.filter(c => c.pipeline === stage);
    const total = results.reduce((sum, c) => sum + c.value, 0);
    return {
      content: [{
        type: "text",
        text: JSON.stringify({ clients: results, total_value: total }, null, 2)
      }]
    };
  }

  throw new Error("Tool not found: " + name);
});

Step 4 — Start the Transport and Connect to Cursor

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP server started and waiting for connections...");
}

main().catch(console.error);

Compile and test:

npx tsc && node dist/index.js

To connect to Cursor, add in your ~/.cursor/mcp.json:

{
  "mcpServers": {
    "my-crm": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

Step 5 — Expose Resources and Prompts

Tools are just one of the three MCP primitives. You can also expose resources (read-only data, like your internal wiki) and prompts (pre-formatted instruction templates).

Resources allow your agent to access static or semi-static data without calling a tool — your internal wiki, procedures, pricing, product catalog.

Prompts let you create pre-formatted instructions that the user can quickly trigger in Cursor or Claude Desktop, for example: "analyze this client" → a pre-built prompt that calls multiple tools in sequence.

Step 6 — Debug with MCP Inspector

MCP Inspector is the official debugging tool that lets you test your server without opening Cursor every time:

npm install -g @modelcontextprotocol/inspector
mcp-inspector node dist/index.js

The Inspector opens a web interface (localhost:5173) that shows:

  • The complete list of your declared tools, resources and prompts
  • A form to test each tool with real arguments
  • Logs for each call with the complete response
  • Errors in case of serialization problems

This is the essential tool before connecting your server to a real agent. You can validate that each tool responds correctly before testing it in production.

Step 7 — Secure Your MCP Server in Production

An MCP server in production without security is a major risk. Measures to put in place:

  • Bearer Token authentication: validate a secret token in each HTTP request
  • Rate limiting: limit the number of requests per IP per minute (100 req/min recommended)
  • Access logging: log each tool call with context (timestamp, tool, arguments)
  • HTTPS mandatory: never expose an MCP server on the Internet without SSL

What to Connect Next

Once this basic MCP server is mastered, the natural next connections are:

  • Your PostgreSQL or MySQL database via a connection pool
  • HubSpot, Salesforce or Pipedrive via their official APIs
  • Your Notion or Confluence documentation for internal RAG
  • Your n8n pipelines to trigger workflows from the AI agent

Do you want a custom MCP server that gives your agents access to your proprietary data? BOVO Digital designs and delivers MCP servers in production.

👉 Talk about your MCP project →

Tags

#MCP#TypeScript#AI Agent#Tutorial#Cursor#2026
William Aklamavo

William Aklamavo

Web development and automation expert, passionate about technological innovation and digital entrepreneurship.

Take action with BOVO Digital

This article sparked ideas? Our experts guide you from strategy to production.