Build an MCP server: wrap any API for AI agents in 100 lines

Mateusz Sroka

13 Mar 2026 (about 1 month ago)

10 min read

Share:

Step-by-step TypeScript tutorial to build a working MCP server with two tools, test it with MCP Inspector, and connect it to Claude Desktop using the free DexPaprika API.

Build an MCP server: wrap any API for AI agents in 100 lines

Build an MCP server: wrap any API for AI agents in 100 lines

An MCP server is a program that exposes tools, data, and prompts to AI agents through the Model Context Protocol. You define what the server can do, and any MCP-compatible client (Claude Desktop, Cursor, VS Code Copilot, ChatGPT) discovers and uses those capabilities automatically. As of early 2026, 50+ AI clients support MCP with official SDKs in 10 languages.

This tutorial walks you through building a working MCP server in TypeScript that wraps a real API. By the end, you'll have a server that Claude Desktop can use to fetch live cryptocurrency data. The concepts transfer directly to any API you want to expose.

I've built four MCP servers since January 2026, and the first one took me a full day because I kept hitting the same pitfalls everyone hits. This guide exists so yours takes an hour instead. Honestly, TypeScript is the better choice for your first server despite Python also being Tier 1. The TypeScript SDK has 11.9k GitHub stars, more examples, and the type safety catches parameter schema mistakes that would silently fail in Python.

What you need to build an MCP server

  • Node.js 18+ installed
  • A code editor (VS Code, Cursor, or similar)
  • Claude Desktop or Cursor for testing (both support MCP natively)
  • Basic TypeScript familiarity (you don't need to be an expert)

No API keys. No accounts to create. The API we'll wrap (DexPaprika) is free and unauthenticated, which removes an entire category of setup friction. That's a deliberate choice: your first server should teach you MCP, not OAuth.

How MCP servers work

Before writing code, it helps to understand the three roles in the protocol:

RoleWhat it doesExample
HostThe AI application managing connectionsClaude Desktop, Cursor, VS Code
ClientComponent inside the host, maintains one connection per serverBuilt into the host (you don't build this)
ServerYour program, exposing capabilities via the protocolWhat you're building in this tutorial

Your server exposes three types of capabilities:

  • Tools (model-controlled): Functions the AI decides when to call. "Get the current price of ETH." This is what you'll build.
  • Resources (app-controlled): Read-only data the application can access. Think file contents or database records.
  • Prompts (user-controlled): Reusable templates the user explicitly invokes.

For your first server, tools are all you need. I'd argue they're 90% of what matters. Resources and prompts are useful later, but I've shipped production servers with only tools and nobody complained.

Transport options: Your server communicates over either stdio (for local servers, the AI launches your program as a subprocess) or Streamable HTTP (for remote servers, clients connect over the network). Stdio is simpler and what we'll use. The old SSE transport was deprecated in March 2025 when Streamable HTTP replaced it.

Step 1: set up the MCP server project

mkdir dex-mcp-server && cd dex-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
mkdir src

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true
  },
  "include": ["src/**/*"]
}

Add to package.json:

{
  "type": "module",
  "scripts": {
    "build": "tsc"
  }
}

This takes about two minutes. If npm install fails on the SDK, make sure you're on Node 18+.

Step 2: create the MCP server with your first tool

Create src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "dex-data",
  version: "1.0.0",
});

// Tool: get token details from DexPaprika
server.registerTool("get_token", {
  description: "Get detailed information about a cryptocurrency token including price, volume, and liquidity. Use this when asked about a specific token's current data.",
  inputSchema: {
    network: z.string().describe("Blockchain network ID, e.g. 'ethereum', 'solana', 'bsc'"),
    address: z.string().describe("Token contract address on the specified network"),
  },
}, async ({ network, address }) => {
  try {
    const response = await fetch(
      `https://api.dexpaprika.com/networks/${network}/tokens/${address}`
    );

    if (!response.ok) {
      return {
        content: [{ type: "text", text: `API error: ${response.status} ${response.statusText}` }],
        isError: true,
      };
    }

    const data = await response.json();
    return {
      content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
    };
  } catch (error) {
    return {
      content: [{ type: "text", text: `Failed to fetch token data: ${error}` }],
      isError: true,
    };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

Three decisions in this code that matter more than you'd expect:

Tool description is everything. The AI reads this to decide when to call your tool. "Get detailed information about a cryptocurrency token including price, volume, and liquidity" tells the model exactly what this tool returns. I rewrote my first server's descriptions three times before the AI used them correctly. Vague descriptions like "get token data" cause the model to pick the wrong tool or skip yours entirely.

isError: true for recoverable errors. This is the part most tutorials skip. When you return isError: true, the AI sees the error message and can try again with corrected parameters. If you throw an unhandled exception instead, the protocol-level error is less useful for self-correction.

No API key in the code. DexPaprika's API is free and unauthenticated. No bearer tokens to leak. This isn't just convenient; it's a security advantage when AI agents have access to your server's context.

Step 3: add a second tool for chaining

One tool is a demo. Two tools make the server useful because the AI can chain them. Add this before the transport connection:

// Tool: search for tokens across all networks
server.registerTool("search_tokens", {
  description: "Search for cryptocurrency tokens by name or symbol across all blockchain networks. Use this when you need to find a token but don't know its contract address.",
  inputSchema: {
    query: z.string().describe("Token name or symbol to search for, e.g. 'PEPE' or 'Uniswap'"),
  },
}, async ({ query }) => {
  try {
    const response = await fetch(
      `https://api.dexpaprika.com/search?query=${encodeURIComponent(query)}`
    );

    if (!response.ok) {
      return {
        content: [{ type: "text", text: `Search failed: ${response.status}` }],
        isError: true,
      };
    }

    const data = await response.json();
    return {
      content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
    };
  } catch (error) {
    return {
      content: [{ type: "text", text: `Search error: ${error}` }],
      isError: true,
    };
  }
});

Now a user can say "what's the current price of PEPE?" and the AI will first call search_tokens to find the contract address, then call get_token to get the price. That's tool chaining, and it's where MCP servers get powerful. The moment I saw my first two-tool chain work correctly, something clicked about why this protocol matters.

Step 4: build and test your MCP server

npm run build

Test with the MCP Inspector (a GUI tool for debugging servers):

npx @modelcontextprotocol/inspector node build/index.js

The Inspector launches a local web UI where you can call each tool, inspect the JSON-RPC messages, and verify your responses are correct. I test every tool here before connecting to Claude Desktop. It saves debugging time, and you can see exactly what the AI will see.

Step 5: connect your MCP server to Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "dex-data": {
      "command": "node",
      "args": ["/absolute/path/to/dex-mcp-server/build/index.js"]
    }
  }
}

Restart Claude Desktop. You'll see a hammer icon in the input area. Click it to verify your tools appear. Then ask: "What's the current price of WETH on Ethereum?"

For Cursor, add via Settings > Tools & Integrations > New MCP server. Set type to "command" and command to node /absolute/path/to/dex-mcp-server/build/index.js.

Five mistakes everyone makes on their first MCP server

1. Writing to stdout. In stdio transport, stdout IS the protocol channel. console.log("debugging...") corrupts the JSON-RPC stream and your server silently breaks. Use console.error() for all logging. This one cost me two hours on my first server. Two hours of "why is Claude not seeing my tools?" because of one console.log.

2. Using relative paths in config. Claude Desktop doesn't resolve paths relative to your project. Always use absolute paths: /Users/you/projects/dex-mcp-server/build/index.js, not ./build/index.js.

3. Forgetting to rebuild. TypeScript servers run from build/, not src/. Every change requires npm run build before testing. I now have npm run build && npx @modelcontextprotocol/inspector node build/index.js as a single command aliased in my shell.

4. Vague tool descriptions. "Fetches data" tells the AI nothing. "Get the current price, 24h volume, and total liquidity for a specific token on a specific blockchain network" tells it exactly when this tool is useful. I'd go so far as to say descriptions matter more than your implementation code. Bad code with great descriptions gets called correctly. Great code with bad descriptions doesn't get called at all.

5. Throwing exceptions instead of returning errors. Tool handlers should return { content: [...], isError: true } for API failures, bad inputs, and missing data. Thrown exceptions become protocol-level errors that the AI can't reason about.

Growing your MCP server toward production

The server you built has 2 tools wrapping one API. That's a starting point, not a destination. Here's what changes as you scale.

The DexPaprika MCP server exposes 14 tools across search, tokens, pools, OHLCV data, and network discovery, all wrapping the same DexPaprika REST API you just used. The CoinPaprika MCP server has 23+ free tools covering 2,500+ cryptocurrencies and 200+ exchanges. Both are open source, and studying their tool descriptions alone is worth the time.

The DexPaprika server is available as an npm package you can self-host or use hosted. The agents.dexpaprika.com hub documents the full integration pattern across REST, streaming, and MCP. DexPaprika's docs and CoinPaprika's docs provide the llms.txt indexes for AI discovery.

To publish your server for others, the MCP Registry (in preview as of early 2026) is the canonical discovery mechanism.

Frequently asked questions

Q: What languages can I build an MCP server in?

A: The official MCP SDKs support 10 languages. TypeScript and Python are Tier 1 (full protocol support, active maintenance). C# and Go are also Tier 1. Java and Rust are Tier 2. Swift, Ruby, PHP, and Kotlin are Tier 3 (community-driven). TypeScript and Python have the largest ecosystem and most examples.

Q: Do I need an API key to build an MCP server?

A: Not necessarily. Your server can wrap any API, authenticated or not. For this tutorial, we used DexPaprika's free API which requires no keys. If your target API requires authentication, store the key in an environment variable and read it at startup, never in the tool description or response.

Q: What's the difference between stdio and Streamable HTTP transport?

A: Stdio runs your server as a local subprocess. The AI client launches it, communicates via stdin/stdout. Streamable HTTP runs your server as a network service handling multiple clients. Use stdio for local development and personal tools. Use Streamable HTTP when you need to share the server across teams or deploy it remotely.

Q: How many tools should an MCP server have?

A: Start with 2-3 that cover your core use case. Production servers typically have 10-20. The DexPaprika MCP server has 14 tools. More tools give the AI more capabilities, but each tool description consumes context window tokens when the AI loads your server, so there's a practical ceiling around 25-30.

Q: Can I test my server without Claude Desktop?

A: Yes. The MCP Inspector (npx @modelcontextprotocol/inspector) provides a web UI for calling tools, inspecting messages, and debugging without any AI client. It's the recommended testing tool during development.

Q: How do I handle errors in MCP tool handlers?

A: Return { content: [{ type: "text", text: "error message" }], isError: true } for all recoverable errors (API failures, invalid inputs, missing data). This surfaces the error to the AI so it can retry with corrected parameters. Never throw unhandled exceptions from tool handlers.

What to remember about building MCP servers

Key takeaways

  • The conceptual shift that matters: you're writing code that AI calls, not code that humans call. That means your documentation (tool descriptions, parameter descriptions) is your interface. In traditional APIs, docs are nice to have. In MCP servers, they're the product.
  • Start with stdio and 2 tools. Ship that. Get it working end-to-end before adding complexity. I've watched developers spend days building 15-tool servers that break on the first connection because they never tested the basic flow.
  • For production reference architectures, the DexPaprika MCP server (14 tools, free, open source) and CoinPaprika MCP server (23+ tools) wrap the same APIs you used in this tutorial. See our guides on MCP, tool use, and AI agent skills for the conceptual foundations behind what you just built.

Related articles

Latest articles

Coinpaprika education

Discover practical guides, definitions, and deep dives to grow your crypto knowledge.

Cryptocurrencies are highly volatile and involve significant risk. You may lose part or all of your investment.

All information on Coinpaprika is provided for informational purposes only and does not constitute financial or investment advice. Always conduct your own research (DYOR) and consult a qualified financial advisor before making investment decisions.

Coinpaprika is not liable for any losses resulting from the use of this information.

Go back to Education